[Proposal] Allow users to specify multiple tables in VACUUM commands
Hi Hackers,
Currently, VACUUM commands allow you to specify one table or all of the tables in the current database to vacuum. I’ve recently found myself wishing I could specify multiple tables in a single VACUUM statement. For example, this would be convenient when there are several large tables in a database and only a few need cleanup for XID purposes. Is this a feature that the community might be interested in?
I’ve attached my first attempt at introducing this functionality. In the patch, I’ve extended the table_name parameter in the VACUUM grammar to a qualified_name_list. While this fits into the grammar decently well, I suspect that it may be desirable to be able to specify a column list for each table as well (e.g. VACUUM foo (a), bar (b)). The attached patch requires that only one table be specified in order to specify a column list. But before I spend too much more time working on this, I thought I’d see how pgsql-hackers@ felt about this feature.
Thanks,
Nathan Bossart
Attachments:
vacuum_multiple_tables_v1.patchapplication/octet-stream; name=vacuum_multiple_tables_v1.patchDownload
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..48c77a9 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>
@@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para>
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
<para>
@@ -152,9 +153,9 @@ 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
+ The names (optionally schema-qualified) of the specific tables to
vacuum. If omitted, all regular tables and materialized views in the
- current database are vacuumed. If the specified table is a partitioned
+ current database are vacuumed. If a specified table is a partitioned
table, all of its leaf partitions are vacuumed.
</para>
</listitem>
@@ -165,7 +166,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, only one table may be specified, and
+ <literal>ANALYZE</> is implied.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 9fbb0eb..ea9a203 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -67,7 +67,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static List *get_rel_oids(Oid relid, const List *vacrels);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -93,6 +93,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
+ /* check that the tables and columns in the statement make sense */
+ if (list_length(vacstmt->va_rels) != 1 && vacstmt->va_cols != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only one table may be specified when columns are specified")));
+
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
* them as -1 which means to use the default values.
@@ -119,7 +125,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
+ vacuum(vacstmt->options, vacstmt->va_rels, InvalidOid, ¶ms,
vacstmt->va_cols, NULL, isTopLevel);
}
@@ -146,7 +152,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
* memory context that will not disappear at transaction commit.
*/
void
-vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
+vacuum(int options, List *va_rels, Oid relid, VacuumParams *params,
List *va_cols, BufferAccessStrategy bstrategy, bool isTopLevel)
{
const char *stmttype;
@@ -229,7 +235,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ relations = get_rel_oids(relid, va_rels);
/*
* Decide whether we need to start/commit our own transactions.
@@ -284,6 +290,23 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PG_TRY();
{
ListCell *cur;
+ RangeVar *relation = NULL;
+
+ /*
+ * The "relation" argument in vacuum_rel(...) and analyze_rel(...) is
+ * only needed for autovacuum workers, which call vacuum(...) with
+ * only one relation specified. If multiple relations are specified
+ * (va_rels is empty or has multiple entries), then we know that we
+ * do not need the "relation" argument, as the current caller is not
+ * an autovacuum worker.
+ *
+ * This means we only need to specify a value for "relation" if we
+ * are vacuuming exactly one relation, which simplifies things a bit.
+ */
+ if (list_length(va_rels) == 1)
+ relation = (RangeVar *) linitial(va_rels);
+ else
+ Assert(!IsAutoVacuumWorkerProcess());
in_vacuum = true;
VacuumCostActive = (VacuumCostDelay > 0);
@@ -379,7 +402,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
* per-relation transactions.
*/
static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+get_rel_oids(Oid relid, const List *vacrels)
{
List *oid_list = NIL;
MemoryContext oldcontext;
@@ -391,49 +414,54 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
oid_list = lappend_oid(oid_list, relid);
MemoryContextSwitchTo(oldcontext);
}
- else if (vacrel)
+ else if (vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid((RangeVar *) lfirst(lc), NoLock, false);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ if (include_parts)
+ oid_list = list_concat(oid_list,
+ find_all_inheritors(relid, NoLock, NULL));
+ else
+ oid_list = lappend_oid(oid_list, relid);
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2d2a9d0..788421a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3763,7 +3763,7 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
- COPY_NODE_FIELD(relation);
+ COPY_NODE_FIELD(va_rels);
COPY_NODE_FIELD(va_cols);
return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b5459cd..4fe2f09 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,7 +1663,7 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
- COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(va_rels);
COMPARE_NODE_FIELD(va_cols);
return true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 65c004c..83b5bd7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10155,11 +10155,11 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
+ n->va_rels = NIL;
n->va_cols = NIL;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose qualified_name_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10169,9 +10169,9 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
+ n->va_rels = $5;
n->va_cols = NIL;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10183,21 +10183,21 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
+ n->va_rels = NIL;
n->va_cols = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' qualified_name_list opt_name_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
+ n->va_rels = $5;
n->va_cols = $6;
if (n->va_cols != NIL) /* implies analyze */
n->options |= VACOPT_ANALYZE;
@@ -10234,19 +10234,19 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
+ n->va_rels = NIL;
n->va_cols = NIL;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose qualified_name_list opt_name_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
+ n->va_rels = $3;
n->va_cols = $4;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89dd3b3..155d579 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3125,8 +3125,8 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, list_make1(&rangevar), tab->at_relid, &tab->at_params,
+ NIL, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 541c2fa..52d6e44 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -158,7 +158,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,
+extern void vacuum(int options, List *va_rels, Oid relid,
VacuumParams *params, List *va_cols,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 46c23c2..f8411d2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3066,7 +3066,7 @@ typedef struct VacuumStmt
{
NodeTag type;
int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
+ List *va_rels; /* list of tables to process, or NIL for all */
List *va_cols; /* list of column names, or NIL for all */
} VacuumStmt;
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..855ac9f 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,16 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vactst (i);
+VACUUM (FREEZE) vactst, vacparted (i);
+ERROR: only one table may be specified when columns are specified
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..e1bd0b7 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,15 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE) vactst (i);
+VACUUM (FREEZE) vactst, vacparted (i);
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Wed, May 10, 2017 at 08:10:48PM +0000, Bossart, Nathan wrote:
Hi Hackers,
Currently, VACUUM commands allow you to specify one table or all of
the tables in the current database to vacuum. I’ve recently found
myself wishing I could specify multiple tables in a single VACUUM
statement. For example, this would be convenient when there are
several large tables in a database and only a few need cleanup for
XID purposes. Is this a feature that the community might be
interested in?
I haven't read the implementation yet, but I've wanted this feature
several times.
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"Bossart, Nathan" <bossartn@amazon.com> writes:
Currently, VACUUM commands allow you to specify one table or all of the tables in the current database to vacuum. I’ve recently found myself wishing I could specify multiple tables in a single VACUUM statement. For example, this would be convenient when there are several large tables in a database and only a few need cleanup for XID purposes. Is this a feature that the community might be interested in?
I'm a bit surprised to realize that we don't allow that, since the
underlying code certainly can do it.
You realize of course that ANALYZE should grow this capability as well.
I’ve attached my first attempt at introducing this functionality. In the patch, I’ve extended the table_name parameter in the VACUUM grammar to a qualified_name_list. While this fits into the grammar decently well, I suspect that it may be desirable to be able to specify a column list for each table as well (e.g. VACUUM foo (a), bar (b)).
The column list only matters for ANALYZE (or VACUUM ANALYZE). But yes,
it should be per-table.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Great, I’ll keep working on this patch and will update this thread with a more complete implementation.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, May 11, 2017 at 6:40 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Bossart, Nathan" <bossartn@amazon.com> writes:
Currently, VACUUM commands allow you to specify one table or all of the tables in the current database to vacuum. I’ve recently found myself wishing I could specify multiple tables in a single VACUUM statement. For example, this would be convenient when there are several large tables in a database and only a few need cleanup for XID purposes. Is this a feature that the community might be interested in?
I'm a bit surprised to realize that we don't allow that, since the
underlying code certainly can do it.You realize of course that ANALYZE should grow this capability as well.
Yup. It is just a matter of extending ExecVacuum() to handle a list of
qualified names with a quick look at the grammar as we are talking
only about manual commands. One question I am wondering though is do
we want to have everything happening in the same transaction? I would
say yes to that to simplify the code. I think that VERBOSE should also
report the per-table information, so this can be noisy with many
tables but that's more helpful than gathering all the results.
I’ve attached my first attempt at introducing this functionality. In the patch, I’ve extended the table_name parameter in the VACUUM grammar to a qualified_name_list. While this fits into the grammar decently well, I suspect that it may be desirable to be able to specify a column list for each table as well (e.g. VACUUM foo (a), bar (b)).
The column list only matters for ANALYZE (or VACUUM ANALYZE). But yes,
it should be per-table.
The grammar allows that by the way:
=# VACUUM (full) aa (a);
VACUUM
Perhaps that's an oversight? I don't think it makes much sense.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael Paquier <michael.paquier@gmail.com> writes:
On Thu, May 11, 2017 at 6:40 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
The column list only matters for ANALYZE (or VACUUM ANALYZE). But yes,
it should be per-table.
The grammar allows that by the way:
=# VACUUM (full) aa (a);
VACUUM
Perhaps that's an oversight? I don't think it makes much sense.
It would be hard to reject at the grammar level, and not very friendly
either because you'd only get "syntax error". We could certainly make
the runtime code throw an error if you gave a column list without saying
ANALYZE. But on the other hand, why bother? I do not remember ever
seeing a question that boiled down to somebody being confused by this.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, May 11, 2017 at 11:13 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Thu, May 11, 2017 at 6:40 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Bossart, Nathan" <bossartn@amazon.com> writes:
Currently, VACUUM commands allow you to specify one table or all of the tables in the current database to vacuum. I’ve recently found myself wishing I could specify multiple tables in a single VACUUM statement. For example, this would be convenient when there are several large tables in a database and only a few need cleanup for XID purposes. Is this a feature that the community might be interested in?
I'm a bit surprised to realize that we don't allow that, since the
underlying code certainly can do it.You realize of course that ANALYZE should grow this capability as well.
Yup. It is just a matter of extending ExecVacuum() to handle a list of
qualified names with a quick look at the grammar as we are talking
only about manual commands. One question I am wondering though is do
we want to have everything happening in the same transaction? I would
say yes to that to simplify the code. I think that VERBOSE should also
report the per-table information, so this can be noisy with many
tables but that's more helpful than gathering all the results.
I agree to report per-table information. Especially In case of one of
tables specified failed during vacuuming, I think we should report at
least information of tables that is done successfully so far.
I’ve attached my first attempt at introducing this functionality. In the patch, I’ve extended the table_name parameter in the VACUUM grammar to a qualified_name_list. While this fits into the grammar decently well, I suspect that it may be desirable to be able to specify a column list for each table as well (e.g. VACUUM foo (a), bar (b)).
The column list only matters for ANALYZE (or VACUUM ANALYZE). But yes,
it should be per-table.The grammar allows that by the way:
=# VACUUM (full) aa (a);
VACUUM
Perhaps that's an oversight? I don't think it makes much sense.
--
Michael--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Regards,
--
Masahiko Sawada
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5/10/17, 8:10 PM, "Masahiko Sawada" <sawada.mshk@gmail.com> wrote:
I agree to report per-table information. Especially In case of one of
tables specified failed during vacuuming, I think we should report at
least information of tables that is done successfully so far.
+1
I believe you already get all per-table information when you do not specify a table name (“VACUUM VERBOSE;”), so I would expect to get this for free as long as we are building this into the existing ExecVacuum(…) and vacuum(…) functions.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, May 11, 2017 at 11:56 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
It would be hard to reject at the grammar level, and not very friendly
either because you'd only get "syntax error". We could certainly make
the runtime code throw an error if you gave a column list without saying
ANALYZE. But on the other hand, why bother? I do not remember ever
seeing a question that boiled down to somebody being confused by this.
The docs also say that adding a column list implies an ANALYZE even if
other keywords are added, and that's the case. Sorry for the noise.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi again,
Attached is a more complete first attempt at adding this functionality. I added two node types: one for parsing the “relation and columns” list in the grammar, and one for holding the relation information we need for each call to vacuum_rel(…)/analyze_rel(…). I also added assertions and comments for some undocumented assumptions that we currently rely upon.
Adjustments to the documentation for VACUUM/ANALYZE and new checks in the VACUUM regression test are included in this patch as well.
Looking forward to any feedback that you have.
Nathan
Attachments:
vacuum_multiple_tables_v2.patchapplication/octet-stream; name=vacuum_multiple_tables_v2.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..7b3ed95 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] [, ...] ]
</synopsis>
</refsynopsisdiv>
@@ -39,9 +39,10 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
<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,
- in which case only the statistics for those columns are collected.
+ current database. With parameters, <command>ANALYZE</command> examines
+ only the tables specified. It is further possible to give a list of
+ column names for each table, in which case only the statistics for those
+ columns are collected.
</para>
</refsect1>
@@ -62,10 +63,10 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
<term><replaceable class="PARAMETER">table_name</replaceable></term>
<listitem>
<para>
- The name (possibly schema-qualified) of a specific table to
+ The name (possibly schema-qualified) of the specific tables to
analyze. If omitted, all regular tables, partitioned tables, and
materialized views in the current database are analyzed (but not
- foreign tables). If the specified table is a partitioned table, both the
+ foreign tables). If a specified table is a partitioned table, both the
inheritance statistics of the partitioned table as a whole and
statistics of the individual partitions are updated.
</para>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..a7a7916 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>
@@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para>
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
<para>
@@ -152,9 +153,9 @@ 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
+ The names (optionally schema-qualified) of the specific tables to
vacuum. If omitted, all regular tables and materialized views in the
- current database are vacuumed. If the specified table is a partitioned
+ current database are vacuumed. If a specified table is a partitioned
table, all of its leaf partitions are vacuumed.
</para>
</listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 9fbb0eb..2b1af16 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/execnodes.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +68,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static List *get_vac_info(Oid relid, const List *vacrels);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -85,13 +86,16 @@ 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));
+ foreach(lc, vacstmt->rels)
+ Assert((((RelationAndColumns *) lfirst(lc))->options & VACOPT_ANALYZE) ||
+ ((RelationAndColumns *) lfirst(lc))->va_cols == NIL);
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
@@ -119,8 +123,8 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, InvalidOid, ¶ms,
+ NULL, isTopLevel);
}
/*
@@ -128,15 +132,15 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relid, if not InvalidOid, indicates the relation to process;
+ * otherwise, the rels list is used. If we intend to process all
+ * relations, rels may be NIL, else it must be passed, because it's
+ * used for error messages. In the case that relid is valid, rels
+ * must have exactly one element corresponding to the specified relid.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,16 +150,17 @@ 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 *rels, Oid relid, VacuumParams *params,
+ BufferAccessStrategy bstrategy, bool isTopLevel)
{
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
- List *relations;
+ List *vacinfos;
static bool in_vacuum = false;
Assert(params != NULL);
+ Assert(!OidIsValid(relid) || list_length(rels) == 1);
stmttype = (options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
@@ -229,7 +234,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ vacinfos = get_vac_info(relid, rels);
/*
* Decide whether we need to start/commit our own transactions.
@@ -254,7 +259,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
use_own_xacts = true;
else if (in_outer_xact)
use_own_xacts = false;
- else if (list_length(relations) > 1)
+ else if (list_length(vacinfos) > 1)
use_own_xacts = true;
else
use_own_xacts = false;
@@ -283,7 +288,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/* Turn vacuum cost accounting on or off */
PG_TRY();
{
- ListCell *cur;
+ ListCell *lc;
in_vacuum = true;
VacuumCostActive = (VacuumCostDelay > 0);
@@ -295,17 +300,19 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/*
* Loop to process each selected relation.
*/
- foreach(cur, relations)
+ foreach(lc, vacinfos)
{
- Oid relid = lfirst_oid(cur);
+ VacInfo *vacinfo = (VacInfo *) lfirst(lc);
+ Oid relid = vacinfo->oid;
+ int per_table_opts = options | vacinfo->opt; /* VACOPT_ANALYZE may be set per-table */
- if (options & VACOPT_VACUUM)
+ if (per_table_opts & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relid, vacinfo->rel, per_table_opts, params))
continue;
}
- if (options & VACOPT_ANALYZE)
+ if (per_table_opts & VACOPT_ANALYZE)
{
/*
* If using separate xacts, start one for analyze. Otherwise,
@@ -318,8 +325,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(relid, vacinfo->rel, per_table_opts, params,
+ vacinfo->cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +380,103 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Build a list of VacInfo for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+get_vac_info(Oid relid, const List *vacrels)
{
- List *oid_list = NIL;
+ List *vacinfos = NIL;
+ VacInfo *vacinfo = NULL;
+ RelationAndColumns *relinfo = NULL;
MemoryContext oldcontext;
+ /*
+ * As explained in vacuum(...), if relid is valid, vacrels must have
+ * exactly one element.
+ */
+ Assert(!OidIsValid(relid) || list_length(vacrels) == 1);
+
/* OID supplied by VACUUM's caller? */
if (OidIsValid(relid))
{
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
+
+ relinfo = (RelationAndColumns *) linitial(vacrels);
+ vacinfo = makeNode(VacInfo);
+ vacinfo->oid = relid;
+ vacinfo->rel = relinfo->relation;
+ vacinfo->cols = relinfo->va_cols;
+ vacinfo->opt = relinfo->options;
+ vacinfos = lappend(vacinfos, vacinfo);
+
MemoryContextSwitchTo(oldcontext);
}
- else if (vacrel)
+ else if (vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
+ List *vacoids = NIL;
+ ListCell *vacoid;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = (RelationAndColumns *) lfirst(lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ if (include_parts)
+ vacoids = list_concat(vacoids, find_all_inheritors(relid, NoLock, NULL));
+ else
+ vacoids = lappend_oid(vacoids, relid);
+
+ /* carry over any columns and options */
+ foreach(vacoid, vacoids)
+ {
+ vacinfo = makeNode(VacInfo);
+ vacinfo->oid = lfirst_oid(vacoid);
+ vacinfo->rel = relinfo->relation;
+ vacinfo->cols = relinfo->va_cols;
+ vacinfo->opt = relinfo->options;
+ vacinfos = lappend(vacinfos, vacinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -465,7 +508,19 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns or extra options to keep track
+ * of. Also, we do not need the RangeVar, because it is only
+ * used for error messaging for autovacuum, and autovacuum
+ * currently always specifies one relation.
+ */
+ vacinfo = makeNode(VacInfo);
+ vacinfo->oid = HeapTupleGetOid(tuple);
+ vacinfos = lappend(vacinfos, vacinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +528,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ return vacinfos;
}
/*
@@ -1347,7 +1402,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
/*
* Check that it's a vacuumable relation; we used to do this in
- * get_rel_oids() but seems safer to check after we've locked the
+ * get_vac_info() but seems safer to check after we've locked the
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2d2a9d0..5c7e406 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3763,12 +3763,36 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static RelationAndColumns *
+_copyRelationAndColumns(const RelationAndColumns *from)
+{
+ RelationAndColumns *newnode = makeNode(RelationAndColumns);
+
+ COPY_SCALAR_FIELD(options);
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
return newnode;
}
+static VacInfo *
+_copyVacInfo(const VacInfo *from)
+{
+ VacInfo *newnode = makeNode(VacInfo);
+
+ COPY_NODE_FIELD(rel);
+ COPY_SCALAR_FIELD(oid);
+ COPY_NODE_FIELD(cols);
+ COPY_SCALAR_FIELD(opt);
+
+ return newnode;
+}
+
static ExplainStmt *
_copyExplainStmt(const ExplainStmt *from)
{
@@ -5211,6 +5235,12 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_RelationAndColumns:
+ retval = _copyRelationAndColumns(from);
+ break;
+ case T_VacInfo:
+ retval = _copyVacInfo(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b5459cd..85933f3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,6 +1663,15 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalRelationAndColumns(const RelationAndColumns *a, const RelationAndColumns *b)
+{
+ COMPARE_SCALAR_FIELD(options);
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
@@ -1670,6 +1679,17 @@ _equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
}
static bool
+_equalVacInfo(const VacInfo *a, const VacInfo *b)
+{
+ COMPARE_NODE_FIELD(rel);
+ COMPARE_SCALAR_FIELD(oid);
+ COMPARE_NODE_FIELD(cols);
+ COMPARE_SCALAR_FIELD(opt);
+
+ return true;
+}
+
+static bool
_equalExplainStmt(const ExplainStmt *a, const ExplainStmt *b)
{
COMPARE_NODE_FIELD(query);
@@ -3360,6 +3380,12 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_RelationAndColumns:
+ retval = _equalRelationAndColumns(a, b);
+ break;
+ case T_VacInfo:
+ retval = _equalVacInfo(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 65c004c..ba70701 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -367,6 +367,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> relation_and_columns
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -396,7 +398,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 relation_and_columns_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10155,11 +10157,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10169,9 +10170,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10183,24 +10183,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' relation_and_columns_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;
}
;
@@ -10234,19 +10230,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10273,6 +10267,24 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+relation_and_columns:
+ qualified_name opt_name_list
+ {
+ RelationAndColumns *n = makeNode(RelationAndColumns);
+ n->options = 0;
+ n->relation = $1;
+ n->va_cols = $2;
+ if (n->va_cols != NIL) /* implies analyze */
+ n->options |= VACOPT_ANALYZE;
+ $$ = (Node *) n;
+ }
+ ;
+
+relation_and_columns_list:
+ relation_and_columns { $$ = list_make1($1); }
+ | relation_and_columns_list ',' relation_and_columns { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89dd3b3..bddbae2 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3114,6 +3114,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ RelationAndColumns *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3125,7 +3126,10 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
+ rel = makeNode(RelationAndColumns);
+ rel->relation = &rangevar;
+
+ vacuum(tab->at_vacoptions, list_make1(rel), tab->at_relid, &tab->at_params,
bstrategy, true);
}
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 541c2fa..a8d3b9e 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -158,9 +158,9 @@ 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,
- BufferAccessStrategy bstrategy, bool isTopLevel);
+extern void vacuum(int options, List *rels, Oid relid,
+ VacuumParams *params, BufferAccessStrategy bstrategy,
+ bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f289f3c..5792279 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2008,4 +2008,14 @@ typedef struct LimitState
TupleTableSlot *subSlot; /* tuple last obtained from subplan */
} LimitState;
+/* convenience struct for various things we need for analyze_rel(...) and vacuum_rel(...) */
+typedef struct VacInfo
+{
+ NodeTag type;
+ RangeVar *rel; /* relation we intend to process */
+ Oid oid; /* OID of the relation */
+ List *cols; /* columns to analyze */
+ int opt; /* per-table options (currently only VACOPT_ANALYZE is possible) */
+} VacInfo;
+
#endif /* EXECNODES_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f59d719..99dfa65 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -37,6 +37,7 @@ typedef enum NodeTag
T_ResultRelInfo,
T_EState,
T_TupleTableSlot,
+ T_VacInfo,
/*
* TAGS FOR PLAN NODES (plannodes.h)
@@ -468,6 +469,7 @@ typedef enum NodeTag
T_PartitionSpec,
T_PartitionBoundSpec,
T_PartitionRangeDatum,
+ T_RelationAndColumns,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 46c23c2..35d3429 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3062,12 +3062,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct RelationAndColumns
+{
+ NodeTag type;
+ int options; /* OR of extra VacuumOption flags to apply */
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+} RelationAndColumns;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..51dce66 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,19 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vactst (i);
+VACUUM (FREEZE) vactst, vacparted (a);
+VACUUM (FREEZE) vaccluster, vactst (i), vacparted;
+VACUUM (FREEZE) vactst (i), vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..e0b812c 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,19 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE) vactst (i);
+VACUUM (FREEZE) vactst, vacparted (a);
+VACUUM (FREEZE) vaccluster, vactst (i), vacparted;
+VACUUM (FREEZE) vactst (i), vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
"Bossart, Nathan" <bossartn@amazon.com> writes:
Looking forward to any feedback that you have.
You probably won't get much in the near term, because we're in
stabilize-the-release mode not develop-new-features mode.
Please add your patch to the pending commitfest
https://commitfest.postgresql.org/14/
so that we remember to look at it when the time comes.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5/11/17, 6:32 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:
You probably won't get much in the near term, because we're in
stabilize-the-release mode not develop-new-features mode.
Please add your patch to the pending commitfest
https://commitfest.postgresql.org/14/
so that we remember to look at it when the time comes.
Understood. I’ve added it to the commitfest.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, May 12, 2017 at 9:47 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Attached is a more complete first attempt at adding this functionality. I added two node types: one for parsing the “relation and columns” list in the grammar, and one for holding the relation information we need for each call to vacuum_rel(…)/analyze_rel(…). I also added assertions and comments for some undocumented assumptions that we currently rely upon.
Adjustments to the documentation for VACUUM/ANALYZE and new checks in the VACUUM regression test are included in this patch as well.
Looking forward to any feedback that you have.
Browsing the code....
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable
class="PARAMETER">table_name</replaceable> [ ( <replaceable
class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable
class="PARAMETER">table_name</replaceable> [ [ ( <replaceable
class="PARAMETER">column_name</replaceable> [, ...] ) ] [, ...] ]
</synopsis>
It seems to me that you don't need those extra square brackets around
the column list. Same remark for vacuum.sgml.
<listitem>
<para>
- The name (possibly schema-qualified) of a specific table to
+ The name (possibly schema-qualified) of the specific tables to
analyze. If omitted, all regular tables, partitioned tables, and
materialized views in the current database are analyzed (but not
- foreign tables). If the specified table is a partitioned table, both the
+ foreign tables). If a specified table is a partitioned table, both the
inheritance statistics of the partitioned table as a whole and
statistics of the individual partitions are updated.
</para>
Don't think that's needed. table_name is still referencing a single
table name. And similar remark for vacuum.sgml.
In short for all that, it is enough to have "[, ... ]" to document
that a list is accepted.
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, InvalidOid, ¶ms,
+ NULL, isTopLevel);
}
It seems to me that it would have been less invasive to loop through
vacuum() for each relation. Do you foresee advantages in allowing
vacuum() to handle multiple? I am not sure if is is worth complicating
the current logic more considering that you have as well so extra
logic to carry on option values.
+ * used for error messages. In the case that relid is valid, rels
+ * must have exactly one element corresponding to the specified relid.
s/rels/relations/ as variable name?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5/11/17, 7:20 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Browsing the code....
Thanks for taking a look.
It seems to me that you don't need those extra square brackets around
the column list. Same remark for vacuum.sgml.
I’ll remove them. My intent was to ensure that we didn’t accidentally suggest that statements like “VACUUM , foo, bar;” were accepted, but the extra brackets don’t seem to fix that, and I don’t foresee much confusion anyway.
In short for all that, it is enough to have "[, ... ]" to document
that a list is accepted.
That makes sense, I’ll fix it.
It seems to me that it would have been less invasive to loop through
vacuum() for each relation. Do you foresee advantages in allowing
vacuum() to handle multiple? I am not sure if is is worth complicating
the current logic more considering that you have as well so extra
logic to carry on option values.
That was the approach I first prototyped. The main disadvantage that I found was that the command wouldn’t fail-fast if one of the tables or columns didn’t exist, and I thought that it might be frustrating to encounter such an error in the middle of vacuuming several large tables. It’s easy enough to change the logic to emit a warning and simply move on to the next table, but that seemed like it could be easily missed among the rest of the vacuum log statements (especially with the verbose option specified). What are your thoughts on this?
In the spirit of simplifying things a bit, I do think it is possible to eliminate one of the new node types, since the fields for each are almost identical.
+ * used for error messages. In the case that relid is valid, rels + * must have exactly one element corresponding to the specified relid. s/rels/relations/ as variable name?
Agreed, that seems nicer.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, May 12, 2017 at 1:06 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 5/11/17, 7:20 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
It seems to me that it would have been less invasive to loop through
vacuum() for each relation. Do you foresee advantages in allowing
vacuum() to handle multiple? I am not sure if is is worth complicating
the current logic more considering that you have as well so extra
logic to carry on option values.That was the approach I first prototyped. The main disadvantage that I found was that the command wouldn’t fail-fast if one of the tables or columns didn’t exist, and I thought that it might be frustrating to encounter such an error in the middle of vacuuming several large tables. It’s easy enough to change the logic to emit a warning and simply move on to the next table, but that seemed like it could be easily missed among the rest of the vacuum log statements (especially with the verbose option specified). What are your thoughts on this?
Hm. If multiple tables are specified and that some of them take a long
time, it could be possible that an error still happens if the
definition of one of those tables changes while VACUUM is in the
middle of running. And this makes moot the error checks that happened
at first step. So it seems to me that we definitely should have a
WARNING if multiple tables are defined anyway, and that to avoid code
duplication we may want to just do those checks once, before
processing one of the listed tables. It is true that is would be easy
to miss a WARNING in the VERBOSE logs, but issuing an ERROR would
really be frustrating in the middle of a nightly run of VACUUM.
In the spirit of simplifying things a bit, I do think it is possible to eliminate one of the new node types, since the fields for each are almost identical.
Two looks too much for a code just aiming at scaling up vacuum to
handle N items. It may be possible to make things even more simple,
but I have not put much thoughts into that to be honest.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I’ve attached a v3 patch that addresses your feedback:
Changes:
- removed extra square brackets in documentation changes
- removed unnecessary documentation changes for parameter list
- eliminated one of the new node types
- renamed ‘rel’ argument to ‘relations’ in vacuum(…)
- moved relations list to vacuum memory context in vacuum(…)
- minor addition to VACUUM regression test
- rebased with master
On 5/15/17, 11:00 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Hm. If multiple tables are specified and that some of them take a long
time, it could be possible that an error still happens if the
definition of one of those tables changes while VACUUM is in the
middle of running. And this makes moot the error checks that happened
at first step. So it seems to me that we definitely should have a
WARNING if multiple tables are defined anyway, and that to avoid code
duplication we may want to just do those checks once, before
processing one of the listed tables. It is true that is would be easy
to miss a WARNING in the VERBOSE logs, but issuing an ERROR would
really be frustrating in the middle of a nightly run of VACUUM.
I think this issue already exists, as this comment in get_rel_oids(…) seems to indicate:
/*
* Since we don't take a lock here, the relation might be gone, or the
* RangeVar might no longer refer to the OID we look up here. In the
* former case, VACUUM will do nothing; in the latter case, it will
* process the OID we looked up here, rather than the new one. Neither
* is ideal, but there's little practical alternative, since we're
* going to commit this transaction and begin a new one between now
* and then.
*/
relid = RangeVarGetRelid(vacrel, NoLock, false);
With the patch applied, I believe this statement still holds true. So if the relation disappears before we get to vacuum_rel(…), we will simply skip it and move on to the next one. The vacuum_rel(…) code provides a WARNING in many cases (e.g. the user does not have privileges to VACUUM the table), but we seem to silently skip the table when it disappears before the call to vacuum_rel(…). If we added a WARNING to the effect of “skipping vacuum of <table_name> — relation no longer exists” for this case, I think what you are suggesting would be satisfied.
However, ANALYZE has a slight caveat. While analyze_rel(…) silently skips the relation if it no longer exists like vacuum_rel(…) does, we do not pre-validate the columns list at all. So, in an ANALYZE statement with multiple tables and columns specified, it’ll only fail once we get to the undefined column. To fix this, we could add a check for the column lists near get_rel_oids(…) and adjust do_analyze_rel(…) to emit a WARNING and skip any columns that vanish in the meantime.
Does this seem like a sane approach?
1. Emit WARNING when skipping if relation disappears before we get to it.
2. Early in vacuum(…), check that the specified columns exist.
3. Emit WARNING and skip any specified columns that vanish before processing.
Nathan
Attachments:
vacuum_multiple_tables_v3.patchapplication/octet-stream; name=vacuum_multiple_tables_v3.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..9843658 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ] [, ...]
</synopsis>
</refsynopsisdiv>
@@ -38,10 +38,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,
- in which case only the statistics for those columns are collected.
+ With no parameters, <command>ANALYZE</command> examines every table in the
+ current database. With parameters, <command>ANALYZE</command> examines
+ only the tables specified. It is further possible to give a list of
+ column names for each table, in which case only the statistics for those
+ columns are collected.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..84bd7eb 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>
@@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para>
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
<para>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 9fbb0eb..ef3106e 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -67,7 +67,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static int get_rel_oids(List **vacrels);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -85,13 +85,16 @@ 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));
+ foreach(lc, vacstmt->rels)
+ Assert((((RelationAndColumns *) lfirst(lc))->options & VACOPT_ANALYZE) ||
+ ((RelationAndColumns *) lfirst(lc))->va_cols == NIL);
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
@@ -119,8 +122,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +130,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * If we intend to process all relations, the 'relations' argument may be
+ * NIL.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +145,17 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ List *temp_relations = NIL; /* for copying the relations list */
+ ListCell *relation;
+ int oid_count;
Assert(params != NULL);
@@ -226,10 +228,20 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ foreach(relation, relations)
+ temp_relations = lappend(temp_relations, copyObject(lfirst(relation)));
+ MemoryContextSwitchTo(oldcontext);
+ relations = temp_relations;
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ oid_count = get_rel_oids(&relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -254,7 +266,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
use_own_xacts = true;
else if (in_outer_xact)
use_own_xacts = false;
- else if (list_length(relations) > 1)
+ else if (oid_count > 1)
use_own_xacts = true;
else
use_own_xacts = false;
@@ -283,8 +295,6 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/* Turn vacuum cost accounting on or off */
PG_TRY();
{
- ListCell *cur;
-
in_vacuum = true;
VacuumCostActive = (VacuumCostDelay > 0);
VacuumCostBalance = 0;
@@ -295,36 +305,41 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/*
* Loop to process each selected relation.
*/
- foreach(cur, relations)
+ foreach(relation, relations)
{
- Oid relid = lfirst_oid(cur);
+ RelationAndColumns *relinfo = (RelationAndColumns *) lfirst(relation);
+ int per_table_opts = options | relinfo->options; /* VACOPT_ANALYZE may be set per-table */
+ ListCell *oid;
- if (options & VACOPT_VACUUM)
+ foreach(oid, relinfo->oids)
{
- if (!vacuum_rel(relid, relation, options, params))
- continue;
- }
-
- if (options & VACOPT_ANALYZE)
- {
- /*
- * If using separate xacts, start one for analyze. Otherwise,
- * we can use the outer transaction.
- */
- if (use_own_xacts)
+ if (per_table_opts & VACOPT_VACUUM)
{
- StartTransactionCommand();
- /* functions in indexes may want a snapshot set */
- PushActiveSnapshot(GetTransactionSnapshot());
+ if (!vacuum_rel(lfirst_oid(oid), relinfo->relation, per_table_opts, params))
+ continue;
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
-
- if (use_own_xacts)
+ if (per_table_opts & VACOPT_ANALYZE)
{
- PopActiveSnapshot();
- CommitTransactionCommand();
+ /*
+ * If using separate xacts, start one for analyze. Otherwise,
+ * we can use the outer transaction.
+ */
+ if (use_own_xacts)
+ {
+ StartTransactionCommand();
+ /* functions in indexes may want a snapshot set */
+ PushActiveSnapshot(GetTransactionSnapshot());
+ }
+
+ analyze_rel(lfirst_oid(oid), relinfo->relation, per_table_opts, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
+
+ if (use_own_xacts)
+ {
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ }
}
}
}
@@ -378,62 +393,72 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static int
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ RelationAndColumns *relinfo;
MemoryContext oldcontext;
+ int oid_count = 0;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = (RelationAndColumns *) lfirst(lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (relinfo->oids != NIL)
+ {
+ oid_count += list_length(relinfo->oids);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ if (include_parts)
+ relinfo->oids = find_all_inheritors(relid, NoLock, NULL);
+ else
+ relinfo->oids = list_make1_oid(relid);
+
+ oid_count += list_length(relinfo->oids);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -465,7 +490,20 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns or extra options to keep track
+ * of. Also, we do not need the RangeVar, because it is only
+ * used for error messaging for autovacuum, and autovacuum
+ * currently always specifies one relation.
+ */
+ relinfo = makeNode(RelationAndColumns);
+ relinfo->oids = list_make1_oid(HeapTupleGetOid(tuple));
+ *vacrels = lappend(*vacrels, relinfo);
+ ++oid_count;
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +511,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ return oid_count;
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6ad3844..0e9af95 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3761,8 +3761,20 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static RelationAndColumns *
+_copyRelationAndColumns(const RelationAndColumns *from)
+{
+ RelationAndColumns *newnode = makeNode(RelationAndColumns);
+
+ COPY_SCALAR_FIELD(options);
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_NODE_FIELD(oids);
return newnode;
}
@@ -5209,6 +5221,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_RelationAndColumns:
+ retval = _copyRelationAndColumns(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c9a8c34..755b2b2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,18 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalRelationAndColumns(const RelationAndColumns *a, const RelationAndColumns *b)
+{
+ COMPARE_SCALAR_FIELD(options);
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_NODE_FIELD(oids);
return true;
}
@@ -3360,6 +3370,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_RelationAndColumns:
+ retval = _equalRelationAndColumns(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2822331..2a3c5af 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> relation_and_columns
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list relation_and_columns_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10148,11 +10150,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10162,9 +10163,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10176,24 +10176,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' relation_and_columns_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;
}
;
@@ -10227,19 +10223,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10266,6 +10260,24 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+relation_and_columns:
+ qualified_name opt_name_list
+ {
+ RelationAndColumns *n = makeNode(RelationAndColumns);
+ n->options = 0;
+ n->relation = $1;
+ n->va_cols = $2;
+ if (n->va_cols != NIL) /* implies analyze */
+ n->options |= VACOPT_ANALYZE;
+ $$ = (Node *) n;
+ }
+ ;
+
+relation_and_columns_list:
+ relation_and_columns { $$ = list_make1($1); }
+ | relation_and_columns_list ',' relation_and_columns { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89dd3b3..13475bb 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3114,6 +3114,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ RelationAndColumns *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3125,8 +3126,12 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* 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);
+ rel = makeNode(RelationAndColumns);
+ rel->relation = &rangevar;
+ if (OidIsValid(tab->at_relid))
+ rel->oids = list_make1_oid(tab->at_relid);
+
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 541c2fa..6c77fe9 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -158,8 +158,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);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f59d719..f66f18c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionSpec,
T_PartitionBoundSpec,
T_PartitionRangeDatum,
+ T_RelationAndColumns,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d396be3..b2de803 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3062,12 +3062,24 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct RelationAndColumns
+{
+ NodeTag type;
+ int options; /* OR of extra VacuumOption flags to apply */
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ List *oids; /* corresponding OIDs (filled in by vacuum.c) */
+} RelationAndColumns;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..922a3a4 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,20 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vactst (i);
+VACUUM (FREEZE) vactst, vacparted (a);
+VACUUM (FREEZE) vaccluster, vactst (i), vacparted;
+VACUUM (FREEZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..dcd6456 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,20 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE) vactst (i);
+VACUUM (FREEZE) vactst, vacparted (a);
+VACUUM (FREEZE) vaccluster, vactst (i), vacparted;
+VACUUM (FREEZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Wed, May 17, 2017 at 7:56 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
I think this issue already exists, as this comment in get_rel_oids(…) seems to indicate:
/*
* Since we don't take a lock here, the relation might be gone, or the
* RangeVar might no longer refer to the OID we look up here. In the
* former case, VACUUM will do nothing; in the latter case, it will
* process the OID we looked up here, rather than the new one. Neither
* is ideal, but there's little practical alternative, since we're
* going to commit this transaction and begin a new one between now
* and then.
*/
relid = RangeVarGetRelid(vacrel, NoLock, false);With the patch applied, I believe this statement still holds true. So if the relation disappears before we get to vacuum_rel(…), we will simply skip it and move on to the next one. The vacuum_rel(…) code provides a WARNING in many cases (e.g. the user does not have privileges to VACUUM the table), but we seem to silently skip the table when it disappears before the call to vacuum_rel(…).
Yes, that's the bits using try_relation_open() which returns NULL if
the relation is gone. I don't think that we want VACUUM to be noisy
about that when running it on a database.
If we added a WARNING to the effect of “skipping vacuum of <table_name> — relation no longer exists” for this case, I think what you are suggesting would be satisfied.
We would do no favor by reporting nothing to the user. Without any
information, the user triggering a manual vacuum may believe that the
relation has been vacuumed as it was listed but that would not be the
case.
However, ANALYZE has a slight caveat. While analyze_rel(…) silently skips the relation if it no longer exists like vacuum_rel(…) does, we do not pre-validate the columns list at all. So, in an ANALYZE statement with multiple tables and columns specified, it’ll only fail once we get to the undefined column. To fix this, we could add a check for the column lists near get_rel_oids(…) and adjust do_analyze_rel(…) to emit a WARNING and skip any columns that vanish in the meantime.
Does this seem like a sane approach?
1. Emit WARNING when skipping if relation disappears before we get to it.
2. Early in vacuum(…), check that the specified columns exist.
And issue an ERROR, right?
3. Emit WARNING and skip any specified columns that vanish before processing.
This looks like a sensible approach to me.
+ RelationAndColumns *relinfo = (RelationAndColumns *)
lfirst(relation);
+ int per_table_opts = options | relinfo->options; /*
VACOPT_ANALYZE may be set per-table */
+ ListCell *oid;
I have just noticed this bit in your patch. So you can basically do
something like that:
VACUUM (ANALYZE) foo, (FULL) bar;
Do we really want to go down to this level of control? I would keep
things a bit more restricted as users may be confused by the different
lock levels involved by each operation, and make use of the same
options for all the relations listed. Opinions from others is welcome.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5/16/17, 11:21 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
On Wed, May 17, 2017 at 7:56 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
I think this issue already exists, as this comment in get_rel_oids(…) seems to indicate:
/*
* Since we don't take a lock here, the relation might be gone, or the
* RangeVar might no longer refer to the OID we look up here. In the
* former case, VACUUM will do nothing; in the latter case, it will
* process the OID we looked up here, rather than the new one. Neither
* is ideal, but there's little practical alternative, since we're
* going to commit this transaction and begin a new one between now
* and then.
*/
relid = RangeVarGetRelid(vacrel, NoLock, false);With the patch applied, I believe this statement still holds true. So if the relation disappears before we get to vacuum_rel(…), we will simply skip it and move on to the next one. The vacuum_rel(…) code provides a WARNING in many cases (e.g. the user does not have privileges to VACUUM the table), but we seem to silently skip the table when it disappears before the call to vacuum_rel(…).
Yes, that's the bits using try_relation_open() which returns NULL if
the relation is gone. I don't think that we want VACUUM to be noisy
about that when running it on a database.
Agreed.
If we added a WARNING to the effect of “skipping vacuum of <table_name> — relation no longer exists” for this case, I think what you are suggesting would be satisfied.
We would do no favor by reporting nothing to the user. Without any
information, the user triggering a manual vacuum may believe that the
relation has been vacuumed as it was listed but that would not be the
case.
Agreed. Unfortunately, I think this is already possible when you specify a table to be vacuumed.
However, ANALYZE has a slight caveat. While analyze_rel(…) silently skips the relation if it no longer exists like vacuum_rel(…) does, we do not pre-validate the columns list at all. So, in an ANALYZE statement with multiple tables and columns specified, it’ll only fail once we get to the undefined column. To fix this, we could add a check for the column lists near get_rel_oids(…) and adjust do_analyze_rel(…) to emit a WARNING and skip any columns that vanish in the meantime.
Does this seem like a sane approach?
1. Emit WARNING when skipping if relation disappears before we get to it.
2. Early in vacuum(…), check that the specified columns exist.And issue an ERROR, right?
Correct. This means that both the relations and columns specified would cause an ERROR if they do not exist and a WARNING if they disappear before we can actually process them.
+ RelationAndColumns *relinfo = (RelationAndColumns *) lfirst(relation); + int per_table_opts = options | relinfo->options; /* VACOPT_ANALYZE may be set per-table */ + ListCell *oid; I have just noticed this bit in your patch. So you can basically do something like that: VACUUM (ANALYZE) foo, (FULL) bar; Do we really want to go down to this level of control? I would keep things a bit more restricted as users may be confused by the different lock levels involved by each operation, and make use of the same options for all the relations listed. Opinions from others is welcome.
I agree with you here, too. I stopped short of allowing customers to explicitly provide per-table options, so the example you provided wouldn’t work here. This is more applicable for something like the following:
VACUUM (FREEZE, VERBOSE) foo, bar (a);
In this case, the FREEZE and VERBOSE options are used for both tables. However, we have a column list specified for ‘bar’, and the ANALYZE option is implied when we specify a column list. So when we process ‘bar’, we need to apply the ANALYZE option, but we do not need it for ‘foo’. For now, that is all that this per-table options variable is used for.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I’ve attached a v4 of the patch.
Changes:
- Early in vacuum(…), emit an ERROR if any specified columns do not exist.
- Emit a WARNING and skip any specified tables or columns that disappear before they are actually processed.
- Small additions to the VACUUM regression test.
- Rebased with master.
Nathan
Attachments:
vacuum_multiple_tables_v4.patchapplication/octet-stream; name=vacuum_multiple_tables_v4.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..9843658 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ] [, ...]
</synopsis>
</refsynopsisdiv>
@@ -38,10 +38,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,
- in which case only the statistics for those columns are collected.
+ With no parameters, <command>ANALYZE</command> examines every table in the
+ current database. With parameters, <command>ANALYZE</command> examines
+ only the tables specified. It is further possible to give a list of
+ column names for each table, in which case only the statistics for those
+ columns are collected.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..84bd7eb 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>
@@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para>
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
<para>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index ecdd895..78cb7a2 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 9fbb0eb..2411c4a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -67,7 +67,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static int get_rel_oids(List **vacrels);
+static void check_columns_exist(RelationAndColumns *relation);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -85,13 +86,16 @@ 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));
+ foreach(lc, vacstmt->rels)
+ Assert((((RelationAndColumns *) lfirst(lc))->options & VACOPT_ANALYZE) ||
+ ((RelationAndColumns *) lfirst(lc))->va_cols == NIL);
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
@@ -119,8 +123,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +131,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * If we intend to process all relations, the 'relations' argument may be
+ * NIL.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +146,17 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ List *temp_relations = NIL; /* for copying the relations list */
+ ListCell *relation;
+ int oid_count;
Assert(params != NULL);
@@ -226,10 +229,28 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ foreach(relation, relations)
+ temp_relations = lappend(temp_relations, copyObject(lfirst(relation)));
+ MemoryContextSwitchTo(oldcontext);
+ relations = temp_relations;
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ oid_count = get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ foreach(relation, relations)
+ check_columns_exist(lfirst(relation));
/*
* Decide whether we need to start/commit our own transactions.
@@ -254,7 +275,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
use_own_xacts = true;
else if (in_outer_xact)
use_own_xacts = false;
- else if (list_length(relations) > 1)
+ else if (oid_count > 1)
use_own_xacts = true;
else
use_own_xacts = false;
@@ -283,8 +304,6 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/* Turn vacuum cost accounting on or off */
PG_TRY();
{
- ListCell *cur;
-
in_vacuum = true;
VacuumCostActive = (VacuumCostDelay > 0);
VacuumCostBalance = 0;
@@ -295,36 +314,41 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/*
* Loop to process each selected relation.
*/
- foreach(cur, relations)
+ foreach(relation, relations)
{
- Oid relid = lfirst_oid(cur);
+ RelationAndColumns *relinfo = (RelationAndColumns *) lfirst(relation);
+ int per_table_opts = options | relinfo->options; /* VACOPT_ANALYZE may be set per-table */
+ ListCell *oid;
- if (options & VACOPT_VACUUM)
+ foreach(oid, relinfo->oids)
{
- if (!vacuum_rel(relid, relation, options, params))
- continue;
- }
-
- if (options & VACOPT_ANALYZE)
- {
- /*
- * If using separate xacts, start one for analyze. Otherwise,
- * we can use the outer transaction.
- */
- if (use_own_xacts)
+ if (per_table_opts & VACOPT_VACUUM)
{
- StartTransactionCommand();
- /* functions in indexes may want a snapshot set */
- PushActiveSnapshot(GetTransactionSnapshot());
+ if (!vacuum_rel(lfirst_oid(oid), relinfo->relation, per_table_opts, params))
+ continue;
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
-
- if (use_own_xacts)
+ if (per_table_opts & VACOPT_ANALYZE)
{
- PopActiveSnapshot();
- CommitTransactionCommand();
+ /*
+ * If using separate xacts, start one for analyze. Otherwise,
+ * we can use the outer transaction.
+ */
+ if (use_own_xacts)
+ {
+ StartTransactionCommand();
+ /* functions in indexes may want a snapshot set */
+ PushActiveSnapshot(GetTransactionSnapshot());
+ }
+
+ analyze_rel(lfirst_oid(oid), relinfo->relation, per_table_opts, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
+
+ if (use_own_xacts)
+ {
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ }
}
}
}
@@ -378,62 +402,72 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static int
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ RelationAndColumns *relinfo;
MemoryContext oldcontext;
+ int oid_count = 0;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = (RelationAndColumns *) lfirst(lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (relinfo->oids != NIL)
+ {
+ oid_count += list_length(relinfo->oids);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ if (include_parts)
+ relinfo->oids = find_all_inheritors(relid, NoLock, NULL);
+ else
+ relinfo->oids = list_make1_oid(relid);
+
+ oid_count += list_length(relinfo->oids);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -465,7 +499,20 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns or extra options to keep track
+ * of. Also, we do not need the RangeVar, because it is only
+ * used for error messaging when specific relations are
+ * chosen.
+ */
+ relinfo = makeNode(RelationAndColumns);
+ relinfo->oids = list_make1_oid(HeapTupleGetOid(tuple));
+ *vacrels = lappend(*vacrels, relinfo);
+ ++oid_count;
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +520,61 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ return oid_count;
+}
+
+/*
+ * Check that all specified columns for the relation exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * that relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(RelationAndColumns *relation)
+{
+ ListCell *oid;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ foreach(oid, relation->oids)
+ {
+ Relation rel;
+ ListCell *lc;
+
+ rel = try_relation_open(lfirst_oid(oid), NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1410,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7811ad5..0fac7db 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3761,8 +3761,20 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static RelationAndColumns *
+_copyRelationAndColumns(const RelationAndColumns *from)
+{
+ RelationAndColumns *newnode = makeNode(RelationAndColumns);
+
+ COPY_SCALAR_FIELD(options);
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_NODE_FIELD(oids);
return newnode;
}
@@ -5209,6 +5221,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_RelationAndColumns:
+ retval = _copyRelationAndColumns(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c9a8c34..755b2b2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,18 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalRelationAndColumns(const RelationAndColumns *a, const RelationAndColumns *b)
+{
+ COMPARE_SCALAR_FIELD(options);
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_NODE_FIELD(oids);
return true;
}
@@ -3360,6 +3370,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_RelationAndColumns:
+ retval = _equalRelationAndColumns(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2822331..2a3c5af 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> relation_and_columns
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list relation_and_columns_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10148,11 +10150,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10162,9 +10163,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10176,24 +10176,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' relation_and_columns_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;
}
;
@@ -10227,19 +10223,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10266,6 +10260,24 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+relation_and_columns:
+ qualified_name opt_name_list
+ {
+ RelationAndColumns *n = makeNode(RelationAndColumns);
+ n->options = 0;
+ n->relation = $1;
+ n->va_cols = $2;
+ if (n->va_cols != NIL) /* implies analyze */
+ n->options |= VACOPT_ANALYZE;
+ $$ = (Node *) n;
+ }
+ ;
+
+relation_and_columns_list:
+ relation_and_columns { $$ = list_make1($1); }
+ | relation_and_columns_list ',' relation_and_columns { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89dd3b3..13475bb 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3114,6 +3114,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ RelationAndColumns *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3125,8 +3126,12 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* 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);
+ rel = makeNode(RelationAndColumns);
+ rel->relation = &rangevar;
+ if (OidIsValid(tab->at_relid))
+ rel->oids = list_make1_oid(tab->at_relid);
+
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 541c2fa..6c77fe9 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -158,8 +158,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);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f59d719..f66f18c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionSpec,
T_PartitionBoundSpec,
T_PartitionRangeDatum,
+ T_RelationAndColumns,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4b8727e..735b942 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3062,12 +3062,24 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct RelationAndColumns
+{
+ NodeTag type;
+ int options; /* OR of extra VacuumOption flags to apply */
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ List *oids; /* corresponding OIDs (filled in by vacuum.c) */
+} RelationAndColumns;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..3c6e45d 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,24 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vactst (i);
+VACUUM (FREEZE) vactst, vacparted (a);
+VACUUM (FREEZE) vaccluster, vactst (i), vacparted;
+VACUUM (FREEZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE 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;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..905226f 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,22 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE) vactst (i);
+VACUUM (FREEZE) vactst, vacparted (a);
+VACUUM (FREEZE) vaccluster, vactst (i), vacparted;
+VACUUM (FREEZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (does_not_exist);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Thu, May 18, 2017 at 12:06 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
I agree with you here, too. I stopped short of allowing customers to explicitly provide per-table options, so the example you provided wouldn’t work here. This is more applicable for something like the following:
VACUUM (FREEZE, VERBOSE) foo, bar (a);
In this case, the FREEZE and VERBOSE options are used for both tables. However, we have a column list specified for ‘bar’, and the ANALYZE option is implied when we specify a column list. So when we process ‘bar’, we need to apply the ANALYZE option, but we do not need it for ‘foo’. For now, that is all that this per-table options variable is used for.
Hm. One argument can be made here: having a column list defined in one
of the tables implies that ANALYZE is enforced for all the relations
listed instead of doing that only on the relations listing columns.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, May 18, 2017 at 7:38 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
I’ve attached a v4 of the patch.
Changes:
- Early in vacuum(…), emit an ERROR if any specified columns do not exist.
- Emit a WARNING and skip any specified tables or columns that disappear before they are actually processed.
- Small additions to the VACUUM regression test.
- Rebased with master.
Thank you for updating the patch.
Some review comments.
I got the following warning messages when building. You might want to
include parser/parse_relation.h.
vacuum.c: In function ‘check_columns_exist’:
vacuum.c:567: warning: implicit declaration of function ‘attnameAttNum’
--
=# vacuum (verbose) hoge, hoge;
INFO: vacuuming "public.hoge"
INFO: "hoge": found 0 removable, 0 nonremovable row versions in 0 out
of 0 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 604
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO: vacuuming "public.hoge"
INFO: "hoge": found 0 removable, 0 nonremovable row versions in 0 out
of 0 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 604
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM
Even if the same relation is specified more than once, we should
vacuum the relation only once. Similarly if we specify the parent
table and its child table we want to vacuum each relation only once.
Regards,
--
Masahiko Sawada
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, May 18, 2017 at 11:01 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Thu, May 18, 2017 at 12:06 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
I agree with you here, too. I stopped short of allowing customers to explicitly provide per-table options, so the example you provided wouldn’t work here. This is more applicable for something like the following:
VACUUM (FREEZE, VERBOSE) foo, bar (a);
In this case, the FREEZE and VERBOSE options are used for both tables. However, we have a column list specified for ‘bar’, and the ANALYZE option is implied when we specify a column list. So when we process ‘bar’, we need to apply the ANALYZE option, but we do not need it for ‘foo’. For now, that is all that this per-table options variable is used for.
Hm. One argument can be made here: having a column list defined in one
of the tables implies that ANALYZE is enforced for all the relations
listed instead of doing that only on the relations listing columns.
It seems to me that it's not good idea to forcibly set ANALYZE in
spite of ANALYZE option is not specified. One reason is that it would
make us difficult to grep it from such as server log. I think It's
better to use the same vacuum option to the all listed relations.
Regards,
--
Masahiko Sawada
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, May 18, 2017 at 2:59 PM, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
It seems to me that it's not good idea to forcibly set ANALYZE in
spite of ANALYZE option is not specified. One reason is that it would
make us difficult to grep it from such as server log. I think It's
better to use the same vacuum option to the all listed relations.
Even now, if you use VACUUM without listing ANALYZE directly, with
relation listing a set of columns, then ANALYZE is implied. I agree
with your point that the same options should be used for all the
relations, and it seems to me that if at least one relation listed has
a column list, then ANALYZE should be implied for all relations.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, May 18, 2017 at 3:19 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Thu, May 18, 2017 at 2:59 PM, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
It seems to me that it's not good idea to forcibly set ANALYZE in
spite of ANALYZE option is not specified. One reason is that it would
make us difficult to grep it from such as server log. I think It's
better to use the same vacuum option to the all listed relations.Even now, if you use VACUUM without listing ANALYZE directly, with
relation listing a set of columns, then ANALYZE is implied.
Oh.. I'd missed that behavior. Thanks!
I agree
with your point that the same options should be used for all the
relations, and it seems to me that if at least one relation listed has
a column list, then ANALYZE should be implied for all relations.
+1
Regards,
--
Masahiko Sawada
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, May 18, 2017 at 2:45 AM, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Thu, May 18, 2017 at 3:19 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:On Thu, May 18, 2017 at 2:59 PM, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
It seems to me that it's not good idea to forcibly set ANALYZE in
spite of ANALYZE option is not specified. One reason is that it would
make us difficult to grep it from such as server log. I think It's
better to use the same vacuum option to the all listed relations.Even now, if you use VACUUM without listing ANALYZE directly, with
relation listing a set of columns, then ANALYZE is implied.Oh.. I'd missed that behavior. Thanks!
I agree
with your point that the same options should be used for all the
relations, and it seems to me that if at least one relation listed has
a column list, then ANALYZE should be implied for all relations.+1
Ugh, really? Are we sure that the current behavior is anything other
than a bug? The idea that VACUUM foo (a) implies ANALYZE doesn't
really sit very well with me in the first place. I'd be more inclined
to reject that with an ERROR complaining that the column list can't be
specified except for ANALYZE.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Ugh, really? Are we sure that the current behavior is anything other
than a bug? The idea that VACUUM foo (a) implies ANALYZE doesn't
really sit very well with me in the first place. I'd be more inclined
to reject that with an ERROR complaining that the column list can't be
specified except for ANALYZE.
Yeah, that's probably more sensible. I think the rationale was "if you
specify columns you must want the ANALYZE option, so why make you type
that in explicitly?". But I can see the argument that it's likely to
confuse users who might have a weaker grasp of the semantics.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, May 19, 2017 at 12:03 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Ugh, really? Are we sure that the current behavior is anything other
than a bug? The idea that VACUUM foo (a) implies ANALYZE doesn't
really sit very well with me in the first place. I'd be more inclined
to reject that with an ERROR complaining that the column list can't be
specified except for ANALYZE.Yeah, that's probably more sensible. I think the rationale was "if you
specify columns you must want the ANALYZE option, so why make you type
that in explicitly?". But I can see the argument that it's likely to
confuse users who might have a weaker grasp of the semantics.
I'd not known such VACUUM behavior so I was a bit surprised but
considering consistency with current behavior I thought that is not
bad idea. But complaining with error seems more sensible.
Regards,
--
Masahiko Sawada
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Thanks all for your feedback. I’ve attached v5 of the patch.
Changes:
- fixed implicit declaration of ‘attnameAttNum’ in vacuum.c
- specified relations and columns are de-duplicated
- users are now forced to specify ANALYZE if a column list is given
- added to VACUUM regression test
- rebased with master
Nathan
Attachments:
vacuum_multiple_tables_v5.patchapplication/octet-stream; name=vacuum_multiple_tables_v5.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..9843658 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ] [, ...]
</synopsis>
</refsynopsisdiv>
@@ -38,10 +38,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,
- in which case only the statistics for those columns are collected.
+ With no parameters, <command>ANALYZE</command> examines every table in the
+ current database. With parameters, <command>ANALYZE</command> examines
+ only the tables specified. It is further possible to give a list of
+ column names for each table, in which case only the statistics for those
+ columns are collected.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..d4d9eaa 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>
@@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para>
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
<para>
@@ -165,7 +166,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index ecdd895..78cb7a2 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 9fbb0eb..6f5f1ef 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +68,9 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static int get_rel_oids(List **vacrels);
+static void check_columns_exist(RelationAndColumns *relation);
+static List *dedupe_relations(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * If we intend to process all relations, the 'relations' argument may be
+ * NIL.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,17 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ List *temp_relations = NIL; /* for copying the relations list */
+ ListCell *relation;
+ int oid_count;
Assert(params != NULL);
@@ -196,6 +197,18 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(relation, relations)
+ {
+ if (((RelationAndColumns *) lfirst(relation))->va_cols != NIL &&
+ !(options & VACOPT_ANALYZE))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided")));
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +239,39 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ foreach(relation, relations)
+ temp_relations = lappend(temp_relations, copyObject(lfirst(relation)));
+ MemoryContextSwitchTo(oldcontext);
+ relations = temp_relations;
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ oid_count = get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ foreach(relation, relations)
+ check_columns_exist(lfirst(relation));
+
+ /*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same set of OIDs.
+ * For example, "foo" and "public.foo" could be the same relation.
+ */
+ relations = dedupe_relations(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -254,7 +296,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
use_own_xacts = true;
else if (in_outer_xact)
use_own_xacts = false;
- else if (list_length(relations) > 1)
+ else if (oid_count > 1)
use_own_xacts = true;
else
use_own_xacts = false;
@@ -283,8 +325,6 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/* Turn vacuum cost accounting on or off */
PG_TRY();
{
- ListCell *cur;
-
in_vacuum = true;
VacuumCostActive = (VacuumCostDelay > 0);
VacuumCostBalance = 0;
@@ -295,36 +335,40 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/*
* Loop to process each selected relation.
*/
- foreach(cur, relations)
+ foreach(relation, relations)
{
- Oid relid = lfirst_oid(cur);
-
- if (options & VACOPT_VACUUM)
- {
- if (!vacuum_rel(relid, relation, options, params))
- continue;
- }
+ RelationAndColumns *relinfo = (RelationAndColumns *) lfirst(relation);
+ ListCell *oid;
- if (options & VACOPT_ANALYZE)
+ foreach(oid, relinfo->oids)
{
- /*
- * If using separate xacts, start one for analyze. Otherwise,
- * we can use the outer transaction.
- */
- if (use_own_xacts)
+ if (options & VACOPT_VACUUM)
{
- StartTransactionCommand();
- /* functions in indexes may want a snapshot set */
- PushActiveSnapshot(GetTransactionSnapshot());
+ if (!vacuum_rel(lfirst_oid(oid), relinfo->relation, options, params))
+ continue;
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
-
- if (use_own_xacts)
+ if (options & VACOPT_ANALYZE)
{
- PopActiveSnapshot();
- CommitTransactionCommand();
+ /*
+ * If using separate xacts, start one for analyze. Otherwise,
+ * we can use the outer transaction.
+ */
+ if (use_own_xacts)
+ {
+ StartTransactionCommand();
+ /* functions in indexes may want a snapshot set */
+ PushActiveSnapshot(GetTransactionSnapshot());
+ }
+
+ analyze_rel(lfirst_oid(oid), relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
+
+ if (use_own_xacts)
+ {
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ }
}
}
}
@@ -378,62 +422,71 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static int
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ RelationAndColumns *relinfo;
MemoryContext oldcontext;
+ int oid_count = 0;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = (RelationAndColumns *) lfirst(lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (relinfo->oids != NIL)
+ {
+ oid_count += list_length(relinfo->oids);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ if (include_parts)
+ relinfo->oids = find_all_inheritors(relid, NoLock, NULL);
+ else
+ relinfo->oids = list_make1_oid(relid);
+ MemoryContextSwitchTo(oldcontext);
+
+ oid_count += list_length(relinfo->oids);
+ }
}
else
{
@@ -465,7 +518,19 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ relinfo = makeNode(RelationAndColumns);
+ relinfo->oids = list_make1_oid(HeapTupleGetOid(tuple));
+ *vacrels = lappend(*vacrels, relinfo);
+ ++oid_count;
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +538,113 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ return oid_count;
+}
+
+/*
+ * Check that all specified columns for the relation exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * that relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(RelationAndColumns *relation)
+{
+ ListCell *oid;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ foreach(oid, relation->oids)
+ {
+ Relation rel;
+ ListCell *lc;
+
+ rel = try_relation_open(lfirst_oid(oid), NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+
+ relation_close(rel, NoLock);
+ }
+}
+
+/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static List *
+dedupe_relations(List *relations)
+{
+ int i = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (relations == NIL) /* nothing to do */
+ return relations;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, relations)
+ {
+ RelationAndColumns *relation = lfirst(lc);
+ int j;
+
+ for (j = ++i; j < list_length(relations); ++j)
+ {
+ RelationAndColumns *nth_rel = list_nth_node(RelationAndColumns, relations, j);
+
+ /* if already processed or not equal, skip */
+ if (list_member_int(duplicates, j) || !equal(relation->oids, nth_rel->oids))
+ continue;
+
+ /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+ duplicates = lappend_int(duplicates, j);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return relations;
+
+ for (i = 0; i < list_length(relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(RelationAndColumns, relations, i));
+ }
+
+ return temp_relations;
}
/*
@@ -1309,6 +1480,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7811ad5..257ef41 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3761,8 +3761,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static RelationAndColumns *
+_copyRelationAndColumns(const RelationAndColumns *from)
+{
+ RelationAndColumns *newnode = makeNode(RelationAndColumns);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_NODE_FIELD(oids);
return newnode;
}
@@ -5209,6 +5220,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_RelationAndColumns:
+ retval = _copyRelationAndColumns(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c9a8c34..7791805 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalRelationAndColumns(const RelationAndColumns *a, const RelationAndColumns *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_NODE_FIELD(oids);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_RelationAndColumns:
+ retval = _equalRelationAndColumns(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2822331..7fc1afc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> relation_and_columns
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list relation_and_columns_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10148,11 +10150,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10162,9 +10163,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10176,24 +10176,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' relation_and_columns_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;
}
;
@@ -10227,19 +10223,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10266,6 +10260,21 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+relation_and_columns:
+ qualified_name opt_name_list
+ {
+ RelationAndColumns *n = makeNode(RelationAndColumns);
+ n->relation = $1;
+ n->va_cols = $2;
+ $$ = (Node *) n;
+ }
+ ;
+
+relation_and_columns_list:
+ relation_and_columns { $$ = list_make1($1); }
+ | relation_and_columns_list ',' relation_and_columns { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89dd3b3..13475bb 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3114,6 +3114,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ RelationAndColumns *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3125,8 +3126,12 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* 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);
+ rel = makeNode(RelationAndColumns);
+ rel->relation = &rangevar;
+ if (OidIsValid(tab->at_relid))
+ rel->oids = list_make1_oid(tab->at_relid);
+
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 541c2fa..6c77fe9 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -158,8 +158,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);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f59d719..f66f18c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionSpec,
T_PartitionBoundSpec,
T_PartitionRangeDatum,
+ T_RelationAndColumns,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4b8727e..4623b74 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3062,12 +3062,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct RelationAndColumns
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ List *oids; /* corresponding OIDs (filled in by [auto]vacuum.c) */
+} RelationAndColumns;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..882c591 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..c7d1c59 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,27 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Fri, May 19, 2017 at 12:03 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Ugh, really? Are we sure that the current behavior is anything other
than a bug?
Point was raised already upthread by me ince, which is what lead me to
the reasoning of my last argument:
/messages/by-id/31695.1494471378@sss.pgh.pa.us
And, like you, I saw that as an oversight.
The idea that VACUUM foo (a) implies ANALYZE doesn't
really sit very well with me in the first place. I'd be more inclined
to reject that with an ERROR complaining that the column list can't be
specified except for ANALYZE.Yeah, that's probably more sensible. I think the rationale was "if you
specify columns you must want the ANALYZE option, so why make you type
that in explicitly?". But I can see the argument that it's likely to
confuse users who might have a weaker grasp of the semantics.
I am fine with an ERROR if a column list is specified without ANALYZE
listed in the options. But that should happen as well for the case
where only one relation is listed.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, May 19, 2017 at 9:06 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
I am fine with an ERROR if a column list is specified without ANALYZE
listed in the options. But that should happen as well for the case
where only one relation is listed.
Perhaps this could be changed for 10? Changing the behavior in
back-branches looks sensitive to me.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael Paquier <michael.paquier@gmail.com> writes:
On Fri, May 19, 2017 at 9:06 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:I am fine with an ERROR if a column list is specified without ANALYZE
listed in the options. But that should happen as well for the case
where only one relation is listed.
Perhaps this could be changed for 10? Changing the behavior in
back-branches looks sensitive to me.
It would make more sense to me to change it as part of the feature
addition, when/if this patch gets committed. Otherwise, we just break
code that works today and we can't point to any solid benefit.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, May 19, 2017 at 10:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
On Fri, May 19, 2017 at 9:06 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:I am fine with an ERROR if a column list is specified without ANALYZE
listed in the options. But that should happen as well for the case
where only one relation is listed.Perhaps this could be changed for 10? Changing the behavior in
back-branches looks sensitive to me.It would make more sense to me to change it as part of the feature
addition, when/if this patch gets committed. Otherwise, we just break
code that works today and we can't point to any solid benefit.
Fine for me as well. I would suggest to split the patch into two parts
to ease review then:
- Rework this error handling for one relation.
- The main patch.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5/18/17, 6:12 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Fine for me as well. I would suggest to split the patch into two parts
to ease review then:
- Rework this error handling for one relation.
- The main patch.
I’d be happy to do so, but I think part one would be pretty small, and almost all of the same code needs to be changed in the main patch anyway. I do not foresee a huge impact on review-ability either way. If others disagree, I can split it up.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"Bossart, Nathan" <bossartn@amazon.com> writes:
On 5/18/17, 6:12 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Fine for me as well. I would suggest to split the patch into two parts
to ease review then:
- Rework this error handling for one relation.
- The main patch.
I’d be happy to do so, but I think part one would be pretty small, and almost all of the same code needs to be changed in the main patch anyway. I do not foresee a huge impact on review-ability either way. If others disagree, I can split it up.
Yeah, I'm dubious that that's really necessary. If the change proves
bigger than you're anticipating, maybe it's worth a two-step approach,
but I share your feeling that it probably isn't.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5/18/17, 8:03 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:
”Bossart, Nathan" <bossartn@amazon.com> writes:
On 5/18/17, 6:12 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Fine for me as well. I would suggest to split the patch into two parts
to ease review then:
- Rework this error handling for one relation.
- The main patch.I’d be happy to do so, but I think part one would be pretty small, and almost all of the same code needs to be changed in the main patch anyway. I do not foresee a huge impact on review-ability either way. If others disagree, I can split it up.
Yeah, I'm dubious that that's really necessary. If the change proves
bigger than you're anticipating, maybe it's worth a two-step approach,
but I share your feeling that it probably isn’t.
Just in case it was missed among the discussion, I’d like to point out that v5 of the patch includes the “ERROR if ANALYZE not specified” change.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, May 19, 2017 at 12:17 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
Just in case it was missed among the discussion, I’d like to point out that v5 of the patch includes the “ERROR if ANALYZE not specified” change.
As long as I don't forget...
+VACUUM vactst (i);
Looking at the tests of v5, I think that you should as well add a test
that lists multiple relations with one or more relations listing a
column list for a VACUUM query, without ANALYZE specified in the
options as the parsings of VacuumStmt and AnalyzeStmt are two
different code paths, giving something like that:
VACUUM (FREEZE) rel1, rel2(col1,col2); --error
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5/18/17, 8:26 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+VACUUM vactst (i);
Looking at the tests of v5, I think that you should as well add a test
that lists multiple relations with one or more relations listing a
column list for a VACUUM query, without ANALYZE specified in the
options as the parsings of VacuumStmt and AnalyzeStmt are two
different code paths, giving something like that:
VACUUM (FREEZE) rel1, rel2(col1,col2); --error
Agreed, this seems like a good test case. I’ve added it in v6 of the patch, which is attached.
Nathan
Attachments:
vacuum_multiple_tables_v6.patchapplication/octet-stream; name=vacuum_multiple_tables_v6.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..9843658 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ] [, ...]
</synopsis>
</refsynopsisdiv>
@@ -38,10 +38,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,
- in which case only the statistics for those columns are collected.
+ With no parameters, <command>ANALYZE</command> examines every table in the
+ current database. With parameters, <command>ANALYZE</command> examines
+ only the tables specified. It is further possible to give a list of
+ column names for each table, in which case only the statistics for those
+ columns are collected.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..d4d9eaa 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>
@@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para>
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
<para>
@@ -165,7 +166,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index ecdd895..78cb7a2 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 9fbb0eb..6f5f1ef 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +68,9 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static int get_rel_oids(List **vacrels);
+static void check_columns_exist(RelationAndColumns *relation);
+static List *dedupe_relations(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * If we intend to process all relations, the 'relations' argument may be
+ * NIL.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,17 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ List *temp_relations = NIL; /* for copying the relations list */
+ ListCell *relation;
+ int oid_count;
Assert(params != NULL);
@@ -196,6 +197,18 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(relation, relations)
+ {
+ if (((RelationAndColumns *) lfirst(relation))->va_cols != NIL &&
+ !(options & VACOPT_ANALYZE))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided")));
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +239,39 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ foreach(relation, relations)
+ temp_relations = lappend(temp_relations, copyObject(lfirst(relation)));
+ MemoryContextSwitchTo(oldcontext);
+ relations = temp_relations;
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ oid_count = get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ foreach(relation, relations)
+ check_columns_exist(lfirst(relation));
+
+ /*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same set of OIDs.
+ * For example, "foo" and "public.foo" could be the same relation.
+ */
+ relations = dedupe_relations(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -254,7 +296,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
use_own_xacts = true;
else if (in_outer_xact)
use_own_xacts = false;
- else if (list_length(relations) > 1)
+ else if (oid_count > 1)
use_own_xacts = true;
else
use_own_xacts = false;
@@ -283,8 +325,6 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/* Turn vacuum cost accounting on or off */
PG_TRY();
{
- ListCell *cur;
-
in_vacuum = true;
VacuumCostActive = (VacuumCostDelay > 0);
VacuumCostBalance = 0;
@@ -295,36 +335,40 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/*
* Loop to process each selected relation.
*/
- foreach(cur, relations)
+ foreach(relation, relations)
{
- Oid relid = lfirst_oid(cur);
-
- if (options & VACOPT_VACUUM)
- {
- if (!vacuum_rel(relid, relation, options, params))
- continue;
- }
+ RelationAndColumns *relinfo = (RelationAndColumns *) lfirst(relation);
+ ListCell *oid;
- if (options & VACOPT_ANALYZE)
+ foreach(oid, relinfo->oids)
{
- /*
- * If using separate xacts, start one for analyze. Otherwise,
- * we can use the outer transaction.
- */
- if (use_own_xacts)
+ if (options & VACOPT_VACUUM)
{
- StartTransactionCommand();
- /* functions in indexes may want a snapshot set */
- PushActiveSnapshot(GetTransactionSnapshot());
+ if (!vacuum_rel(lfirst_oid(oid), relinfo->relation, options, params))
+ continue;
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
-
- if (use_own_xacts)
+ if (options & VACOPT_ANALYZE)
{
- PopActiveSnapshot();
- CommitTransactionCommand();
+ /*
+ * If using separate xacts, start one for analyze. Otherwise,
+ * we can use the outer transaction.
+ */
+ if (use_own_xacts)
+ {
+ StartTransactionCommand();
+ /* functions in indexes may want a snapshot set */
+ PushActiveSnapshot(GetTransactionSnapshot());
+ }
+
+ analyze_rel(lfirst_oid(oid), relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
+
+ if (use_own_xacts)
+ {
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ }
}
}
}
@@ -378,62 +422,71 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static int
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ RelationAndColumns *relinfo;
MemoryContext oldcontext;
+ int oid_count = 0;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = (RelationAndColumns *) lfirst(lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (relinfo->oids != NIL)
+ {
+ oid_count += list_length(relinfo->oids);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ if (include_parts)
+ relinfo->oids = find_all_inheritors(relid, NoLock, NULL);
+ else
+ relinfo->oids = list_make1_oid(relid);
+ MemoryContextSwitchTo(oldcontext);
+
+ oid_count += list_length(relinfo->oids);
+ }
}
else
{
@@ -465,7 +518,19 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ relinfo = makeNode(RelationAndColumns);
+ relinfo->oids = list_make1_oid(HeapTupleGetOid(tuple));
+ *vacrels = lappend(*vacrels, relinfo);
+ ++oid_count;
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +538,113 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ return oid_count;
+}
+
+/*
+ * Check that all specified columns for the relation exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * that relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(RelationAndColumns *relation)
+{
+ ListCell *oid;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ foreach(oid, relation->oids)
+ {
+ Relation rel;
+ ListCell *lc;
+
+ rel = try_relation_open(lfirst_oid(oid), NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+
+ relation_close(rel, NoLock);
+ }
+}
+
+/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static List *
+dedupe_relations(List *relations)
+{
+ int i = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (relations == NIL) /* nothing to do */
+ return relations;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, relations)
+ {
+ RelationAndColumns *relation = lfirst(lc);
+ int j;
+
+ for (j = ++i; j < list_length(relations); ++j)
+ {
+ RelationAndColumns *nth_rel = list_nth_node(RelationAndColumns, relations, j);
+
+ /* if already processed or not equal, skip */
+ if (list_member_int(duplicates, j) || !equal(relation->oids, nth_rel->oids))
+ continue;
+
+ /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+ duplicates = lappend_int(duplicates, j);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return relations;
+
+ for (i = 0; i < list_length(relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(RelationAndColumns, relations, i));
+ }
+
+ return temp_relations;
}
/*
@@ -1309,6 +1480,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7811ad5..257ef41 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3761,8 +3761,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static RelationAndColumns *
+_copyRelationAndColumns(const RelationAndColumns *from)
+{
+ RelationAndColumns *newnode = makeNode(RelationAndColumns);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_NODE_FIELD(oids);
return newnode;
}
@@ -5209,6 +5220,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_RelationAndColumns:
+ retval = _copyRelationAndColumns(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c9a8c34..7791805 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalRelationAndColumns(const RelationAndColumns *a, const RelationAndColumns *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_NODE_FIELD(oids);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_RelationAndColumns:
+ retval = _equalRelationAndColumns(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2822331..7fc1afc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> relation_and_columns
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list relation_and_columns_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10148,11 +10150,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10162,9 +10163,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10176,24 +10176,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' relation_and_columns_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;
}
;
@@ -10227,19 +10223,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10266,6 +10260,21 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+relation_and_columns:
+ qualified_name opt_name_list
+ {
+ RelationAndColumns *n = makeNode(RelationAndColumns);
+ n->relation = $1;
+ n->va_cols = $2;
+ $$ = (Node *) n;
+ }
+ ;
+
+relation_and_columns_list:
+ relation_and_columns { $$ = list_make1($1); }
+ | relation_and_columns_list ',' relation_and_columns { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89dd3b3..13475bb 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3114,6 +3114,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ RelationAndColumns *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3125,8 +3126,12 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* 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);
+ rel = makeNode(RelationAndColumns);
+ rel->relation = &rangevar;
+ if (OidIsValid(tab->at_relid))
+ rel->oids = list_make1_oid(tab->at_relid);
+
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 541c2fa..6c77fe9 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -158,8 +158,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);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f59d719..f66f18c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionSpec,
T_PartitionBoundSpec,
T_PartitionRangeDatum,
+ T_RelationAndColumns,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4b8727e..4623b74 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3062,12 +3062,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct RelationAndColumns
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ List *oids; /* corresponding OIDs (filled in by [auto]vacuum.c) */
+} RelationAndColumns;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..8a95fd0 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,34 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..558f60a 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,29 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
I’ve rebased this patch with master to create v7, which is attached.
Nathan
Attachments:
vacuum_multiple_tables_v7.patchapplication/octet-stream; name=vacuum_multiple_tables_v7.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..9843658 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ] [, ...]
</synopsis>
</refsynopsisdiv>
@@ -38,10 +38,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,
- in which case only the statistics for those columns are collected.
+ With no parameters, <command>ANALYZE</command> examines every table in the
+ current database. With parameters, <command>ANALYZE</command> examines
+ only the tables specified. It is further possible to give a list of
+ column names for each table, in which case only the statistics for those
+ columns are collected.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..d4d9eaa 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>
@@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para>
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
<para>
@@ -165,7 +166,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 2b63827..d9605af 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..16d585d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +68,9 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static int get_rel_oids(List **vacrels);
+static void check_columns_exist(RelationAndColumns *relation);
+static List *dedupe_relations(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * If we intend to process all relations, the 'relations' argument may be
+ * NIL.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,17 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ List *temp_relations = NIL; /* for copying the relations list */
+ ListCell *relation;
+ int oid_count;
Assert(params != NULL);
@@ -196,6 +197,18 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(relation, relations)
+ {
+ if (((RelationAndColumns *) lfirst(relation))->va_cols != NIL &&
+ !(options & VACOPT_ANALYZE))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided")));
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +239,39 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ foreach(relation, relations)
+ temp_relations = lappend(temp_relations, copyObject(lfirst(relation)));
+ MemoryContextSwitchTo(oldcontext);
+ relations = temp_relations;
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ oid_count = get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ foreach(relation, relations)
+ check_columns_exist(lfirst(relation));
+
+ /*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same set of OIDs.
+ * For example, "foo" and "public.foo" could be the same relation.
+ */
+ relations = dedupe_relations(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -254,7 +296,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
use_own_xacts = true;
else if (in_outer_xact)
use_own_xacts = false;
- else if (list_length(relations) > 1)
+ else if (oid_count > 1)
use_own_xacts = true;
else
use_own_xacts = false;
@@ -283,8 +325,6 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/* Turn vacuum cost accounting on or off */
PG_TRY();
{
- ListCell *cur;
-
in_vacuum = true;
VacuumCostActive = (VacuumCostDelay > 0);
VacuumCostBalance = 0;
@@ -295,36 +335,40 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/*
* Loop to process each selected relation.
*/
- foreach(cur, relations)
+ foreach(relation, relations)
{
- Oid relid = lfirst_oid(cur);
-
- if (options & VACOPT_VACUUM)
- {
- if (!vacuum_rel(relid, relation, options, params))
- continue;
- }
+ RelationAndColumns *relinfo = (RelationAndColumns *) lfirst(relation);
+ ListCell *oid;
- if (options & VACOPT_ANALYZE)
+ foreach(oid, relinfo->oids)
{
- /*
- * If using separate xacts, start one for analyze. Otherwise,
- * we can use the outer transaction.
- */
- if (use_own_xacts)
+ if (options & VACOPT_VACUUM)
{
- StartTransactionCommand();
- /* functions in indexes may want a snapshot set */
- PushActiveSnapshot(GetTransactionSnapshot());
+ if (!vacuum_rel(lfirst_oid(oid), relinfo->relation, options, params))
+ continue;
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
-
- if (use_own_xacts)
+ if (options & VACOPT_ANALYZE)
{
- PopActiveSnapshot();
- CommitTransactionCommand();
+ /*
+ * If using separate xacts, start one for analyze. Otherwise,
+ * we can use the outer transaction.
+ */
+ if (use_own_xacts)
+ {
+ StartTransactionCommand();
+ /* functions in indexes may want a snapshot set */
+ PushActiveSnapshot(GetTransactionSnapshot());
+ }
+
+ analyze_rel(lfirst_oid(oid), relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
+
+ if (use_own_xacts)
+ {
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ }
}
}
}
@@ -378,62 +422,71 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static int
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ RelationAndColumns *relinfo;
MemoryContext oldcontext;
+ int oid_count = 0;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = (RelationAndColumns *) lfirst(lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (relinfo->oids != NIL)
+ {
+ oid_count += list_length(relinfo->oids);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ if (include_parts)
+ relinfo->oids = find_all_inheritors(relid, NoLock, NULL);
+ else
+ relinfo->oids = list_make1_oid(relid);
+ MemoryContextSwitchTo(oldcontext);
+
+ oid_count += list_length(relinfo->oids);
+ }
}
else
{
@@ -465,7 +518,19 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ relinfo = makeNode(RelationAndColumns);
+ relinfo->oids = list_make1_oid(HeapTupleGetOid(tuple));
+ *vacrels = lappend(*vacrels, relinfo);
+ ++oid_count;
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +538,113 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ return oid_count;
+}
+
+/*
+ * Check that all specified columns for the relation exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * that relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(RelationAndColumns *relation)
+{
+ ListCell *oid;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ foreach(oid, relation->oids)
+ {
+ Relation rel;
+ ListCell *lc;
+
+ rel = try_relation_open(lfirst_oid(oid), NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+
+ relation_close(rel, NoLock);
+ }
+}
+
+/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static List *
+dedupe_relations(List *relations)
+{
+ int i = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (relations == NIL) /* nothing to do */
+ return relations;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, relations)
+ {
+ RelationAndColumns *relation = lfirst(lc);
+ int j;
+
+ for (j = ++i; j < list_length(relations); ++j)
+ {
+ RelationAndColumns *nth_rel = list_nth_node(RelationAndColumns, relations, j);
+
+ /* if already processed or not equal, skip */
+ if (list_member_int(duplicates, j) || !equal(relation->oids, nth_rel->oids))
+ continue;
+
+ /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+ duplicates = lappend_int(duplicates, j);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return relations;
+
+ for (i = 0; i < list_length(relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(RelationAndColumns, relations, i));
+ }
+
+ return temp_relations;
}
/*
@@ -1309,6 +1480,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 45a04b0..25eaa9d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3762,8 +3762,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static RelationAndColumns *
+_copyRelationAndColumns(const RelationAndColumns *from)
+{
+ RelationAndColumns *newnode = makeNode(RelationAndColumns);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_NODE_FIELD(oids);
return newnode;
}
@@ -5210,6 +5221,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_RelationAndColumns:
+ retval = _copyRelationAndColumns(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..3a120f3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalRelationAndColumns(const RelationAndColumns *a, const RelationAndColumns *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_NODE_FIELD(oids);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_RelationAndColumns:
+ retval = _equalRelationAndColumns(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..f32218e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> relation_and_columns
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list relation_and_columns_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10128,11 +10130,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10142,9 +10143,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10156,24 +10156,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' relation_and_columns_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;
}
;
@@ -10207,19 +10203,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose relation_and_columns_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10246,6 +10240,21 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+relation_and_columns:
+ qualified_name opt_name_list
+ {
+ RelationAndColumns *n = makeNode(RelationAndColumns);
+ n->relation = $1;
+ n->va_cols = $2;
+ $$ = (Node *) n;
+ }
+ ;
+
+relation_and_columns_list:
+ relation_and_columns { $$ = list_make1($1); }
+ | relation_and_columns_list ',' relation_and_columns { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 00b1e82..ea5460b 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3115,6 +3115,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ RelationAndColumns *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3126,8 +3127,12 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* 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);
+ rel = makeNode(RelationAndColumns);
+ rel->relation = &rangevar;
+ if (OidIsValid(tab->at_relid))
+ rel->oids = list_make1_oid(tab->at_relid);
+
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..620a8db 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_RelationAndColumns,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..ad2b4ee 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct RelationAndColumns
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ List *oids; /* corresponding OIDs (filled in by [auto]vacuum.c) */
+} RelationAndColumns;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..8a95fd0 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,34 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..558f60a 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,29 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Tue, Aug 15, 2017 at 8:28 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
I’ve rebased this patch with master to create v7, which is attached.
Thanks for the rebased patch. I am switching into review mode actively
now, so I'll look at it soon.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Aug 15, 2017 at 4:05 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Tue, Aug 15, 2017 at 8:28 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
I’ve rebased this patch with master to create v7, which is attached.
Thanks for the rebased patch. I am switching into review mode actively
now, so I'll look at it soon.
Another pass for this patch.
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
I think that this would be reworded. "nice" is cute is this context.
Why not just saying something like:
"Do not issue an ERROR if a column is missing but use a WARNING
instead. At the beginning of the VACUUM run, the code already checked
for undefined columns and informed about an ERROR, but we want the
processing to move on for existing columns."
+ /*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same set of OIDs.
+ * For example, "foo" and "public.foo" could be the same relation.
+ */
+ relations = dedupe_relations(relations);
This has been introduced in v5. If we would want to put some effort
for that, I think that it could be a separate patch and a separate
discussion. This patch does not make things worse than they are, see
HEAD for example with the same column specified twice:
=# create table aa as select generate_series(1, 10000) as a;
SELECT 10000
=# vacuum (analyze) aa (a, a);
ERROR: 23505: duplicate key value violates unique constraint
"pg_statistic_relid_att_inh_index"
DETAIL: Key (starelid, staattnum, stainherit)=(16385, 1, f) already exists.
SCHEMA NAME: pg_catalog
TABLE NAME: pg_statistic
CONSTRAINT NAME: pg_statistic_relid_att_inh_index
LOCATION: _bt_check_unique, nbtinsert.c:434
And actually, your patch does not seem to work, and makes things worse:
=# analyze aa (a, a);
ERROR: XX000: tuple already updated by self
LOCATION: simple_heap_update, heapam.c:4482
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct RelationAndColumns
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ List *oids; /* corresponding OIDs (filled in by
[auto]vacuum.c) */
+} RelationAndColumns;
This name is a bit awkward. Say VacuumRelation? I also don't
understand why there are multiple OIDs here. There should be only one,
referring to the relation of this RangeVar. Even for inherited
relations what should be done is to add one entry RelationAndColumns
(or VacuumRelation) for each child relation.
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ foreach(relation, relations)
+ check_columns_exist(lfirst(relation));
The full list could be processed in there.
+static void
+check_columns_exist(RelationAndColumns *relation)
[...]
+ Relation rel;
+ ListCell *lc;
+
+ rel = try_relation_open(lfirst_oid(oid), NoLock);
+ if (!rel)
This really meritates a comment. In short: why is it fine to not take
a lock here? The answer I think is that even if the relation does not
exist vacuum would do nothing, but this should be written out.
+ foreach(relation, relations)
+ {
+ if (((RelationAndColumns *) lfirst(relation))->va_cols != NIL &&
+ !(options & VACOPT_ANALYZE))
Saving the data in a variable makes for a better style and easier
debugging. When doing a bitwise operation, please use as well != 0 or
== 0 as you are looking here for a boolean result.
+ relinfo = makeNode(RelationAndColumns);
+ relinfo->oids = list_make1_oid(HeapTupleGetOid(tuple));
+ *vacrels = lappend(*vacrels, relinfo);
Assigning variables even for nothing is good practice for readability,
and shows the intention behind the code.
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * If we intend to process all relations, the 'relations' argument may be
+ * NIL.
This comment actually applies to RelationAndColumns. If the OID is
invalid, then RangeVar is used, and should always be set. You are
breaking that promise actually for autovacuum. The comment here should
say that if relations is NIL all the relations of the database are
processes, and for an ANALYZE all the columns are done.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Aug 18, 2017 at 1:56 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
+ /* + * We have already checked the column list in vacuum(...), + * but the columns may have disappeared since then. If + * this happens, emit a nice warning message and skip the + * undefined column. + */ I think that this would be reworded. "nice" is cute is this context. Why not just saying something like: "Do not issue an ERROR if a column is missing but use a WARNING instead. At the beginning of the VACUUM run, the code already checked for undefined columns and informed about an ERROR, but we want the processing to move on for existing columns."
Hmm, I find your (Michael's) suggestion substantially less clear than
the wording to which you are objecting.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 8/18/17, 12:56 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Another pass for this patch.
Thanks! I've attached v8 of the patch.
On 8/18/17, 1:49 PM, "Robert Haas" <robertmhaas@gmail.com> wrote:
On Fri, Aug 18, 2017 at 1:56 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:+ /* + * We have already checked the column list in vacuum(...), + * but the columns may have disappeared since then. If + * this happens, emit a nice warning message and skip the + * undefined column. + */ I think that this would be reworded. "nice" is cute is this context. Why not just saying something like: "Do not issue an ERROR if a column is missing but use a WARNING instead. At the beginning of the VACUUM run, the code already checked for undefined columns and informed about an ERROR, but we want the processing to move on for existing columns."Hmm, I find your (Michael's) suggestion substantially less clear than
the wording to which you are objecting.
I'll leave this as-is for now.
And actually, your patch does not seem to work, and makes things worse:
=# analyze aa (a, a);
ERROR: XX000: tuple already updated by self
LOCATION: simple_heap_update, heapam.c:4482
I think the underlying issue is that dedupe_relations(...) does not
handle duplicate columns correctly. The attached patch should fix that
issue. I've also added some test cases to cover this.
+/* + * This is used to keep track of a relation and an optional list of + * column names, as may be specified in VACUUM and ANALYZE. + */ +typedef struct RelationAndColumns +{ + NodeTag type; + RangeVar *relation; /* single table to process */ + List *va_cols; /* list of column names, or NIL for all */ + List *oids; /* corresponding OIDs (filled in by [auto]vacuum.c) */ +} RelationAndColumns; This name is a bit awkward. Say VacuumRelation? I also don't understand why there are multiple OIDs here. There should be only one, referring to the relation of this RangeVar. Even for inherited relations what should be done is to add one entry RelationAndColumns (or VacuumRelation) for each child relation.
I'll admit I struggled with naming this struct, so I'm happy to change it.
According to the docs, VACUUM and ANALYZE "do not support recursing over
inheritance hierarchies" [1]https://www.postgresql.org/docs/current/static/ddl-inherit.html. However, we need a list of OIDs for
partitioned tables. Namely, this piece of code in get_rel_oids(...):
if (include_parts)
oid_list = list_concat(oid_list,
find_all_inheritors(relid, NoLock, NULL));
else
oid_list = lappend_oid(oid_list, relid);
Since all of these OIDs should correspond to the same partitioned table,
it makes sense to me to leave them together instead of breaking out each
partition into a VacuumRelation. If we did so, I think we would also need
to duplicate the va_cols list for each partition. What do you think?
+ /* + * Check that all specified columns exist so that we can fast-fail + * commands with multiple tables. If the column disappears before we + * actually process it, we will emit a WARNING and skip it later on. + */ + foreach(relation, relations) + check_columns_exist(lfirst(relation)); The full list could be processed in there.
Sure. I've made this change.
+static void +check_columns_exist(RelationAndColumns *relation) [...] + Relation rel; + ListCell *lc; + + rel = try_relation_open(lfirst_oid(oid), NoLock); + if (!rel) This really meritates a comment. In short: why is it fine to not take a lock here? The answer I think is that even if the relation does not exist vacuum would do nothing, but this should be written out.
Right, we are just checking the existence of columns here. We lock it
later on with the understanding that the columns may have disappeared in
the meantime, in which case they will simply be skipped. I've added a
comment.
+ foreach(relation, relations) + { + if (((RelationAndColumns *) lfirst(relation))->va_cols != NIL && + !(options & VACOPT_ANALYZE)) Saving the data in a variable makes for a better style and easier debugging. When doing a bitwise operation, please use as well != 0 or == 0 as you are looking here for a boolean result.+ relinfo = makeNode(RelationAndColumns); + relinfo->oids = list_make1_oid(HeapTupleGetOid(tuple)); + *vacrels = lappend(*vacrels, relinfo); Assigning variables even for nothing is good practice for readability, and shows the intention behind the code.
I've made these changes.
- * relid, if not InvalidOid, indicate the relation to process; otherwise, - * the RangeVar is used. (The latter must always be passed, because it's - * used for error messages.) + * If we intend to process all relations, the 'relations' argument may be + * NIL. This comment actually applies to RelationAndColumns. If the OID is invalid, then RangeVar is used, and should always be set. You are breaking that promise actually for autovacuum. The comment here should say that if relations is NIL all the relations of the database are processes, and for an ANALYZE all the columns are done.
Makes sense, I've tried to make this comment clearer.
Nathan
[1]: https://www.postgresql.org/docs/current/static/ddl-inherit.html
Attachments:
vacuum_multiple_tables_v8.patchapplication/octet-stream; name=vacuum_multiple_tables_v8.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..9843658 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ] [, ...]
</synopsis>
</refsynopsisdiv>
@@ -38,10 +38,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,
- in which case only the statistics for those columns are collected.
+ With no parameters, <command>ANALYZE</command> examines every table in the
+ current database. With parameters, <command>ANALYZE</command> examines
+ only the tables specified. It is further possible to give a list of
+ column names for each table, in which case only the statistics for those
+ columns are collected.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..d4d9eaa 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>
@@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para>
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
<para>
@@ -165,7 +166,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fbad13e..79966746 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..b5c75cf 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +68,9 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static int get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relation);
+static List *dedupe_relations(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelations to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,17 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ List *temp_relations = NIL; /* for copying the relations list */
+ ListCell *lc;
+ int oid_count;
Assert(params != NULL);
@@ -196,6 +197,18 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = (VacuumRelation *) lfirst(lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided")));
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +239,38 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ foreach(lc, relations)
+ temp_relations = lappend(temp_relations, copyObject(lfirst(lc)));
+ MemoryContextSwitchTo(oldcontext);
+ relations = temp_relations;
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ oid_count = get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
+
+ /*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same set of OIDs.
+ * For example, "foo" and "public.foo" could be the same relation.
+ */
+ relations = dedupe_relations(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -254,7 +295,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
use_own_xacts = true;
else if (in_outer_xact)
use_own_xacts = false;
- else if (list_length(relations) > 1)
+ else if (oid_count > 1)
use_own_xacts = true;
else
use_own_xacts = false;
@@ -283,8 +324,6 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/* Turn vacuum cost accounting on or off */
PG_TRY();
{
- ListCell *cur;
-
in_vacuum = true;
VacuumCostActive = (VacuumCostDelay > 0);
VacuumCostBalance = 0;
@@ -295,36 +334,41 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
/*
* Loop to process each selected relation.
*/
- foreach(cur, relations)
+ foreach(lc, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = (VacuumRelation *) lfirst(lc);
+ ListCell *oid;
- if (options & VACOPT_VACUUM)
+ foreach(oid, relinfo->oids)
{
- if (!vacuum_rel(relid, relation, options, params))
- continue;
- }
- if (options & VACOPT_ANALYZE)
- {
- /*
- * If using separate xacts, start one for analyze. Otherwise,
- * we can use the outer transaction.
- */
- if (use_own_xacts)
+ if (options & VACOPT_VACUUM)
{
- StartTransactionCommand();
- /* functions in indexes may want a snapshot set */
- PushActiveSnapshot(GetTransactionSnapshot());
+ if (!vacuum_rel(lfirst_oid(oid), relinfo->relation, options, params))
+ continue;
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
-
- if (use_own_xacts)
+ if (options & VACOPT_ANALYZE)
{
- PopActiveSnapshot();
- CommitTransactionCommand();
+ /*
+ * If using separate xacts, start one for analyze. Otherwise,
+ * we can use the outer transaction.
+ */
+ if (use_own_xacts)
+ {
+ StartTransactionCommand();
+ /* functions in indexes may want a snapshot set */
+ PushActiveSnapshot(GetTransactionSnapshot());
+ }
+
+ analyze_rel(lfirst_oid(oid), relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
+
+ if (use_own_xacts)
+ {
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ }
}
}
}
@@ -378,62 +422,71 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static int
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
+ int oid_count = 0;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = (VacuumRelation *) lfirst(lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (relinfo->oids != NIL)
+ {
+ oid_count += list_length(relinfo->oids);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ if (include_parts)
+ relinfo->oids = find_all_inheritors(relid, NoLock, NULL);
+ else
+ relinfo->oids = list_make1_oid(relid);
+ MemoryContextSwitchTo(oldcontext);
+
+ oid_count += list_length(relinfo->oids);
+ }
}
else
{
@@ -451,6 +504,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +519,20 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ relinfo = makeNode(VacuumRelation);
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo->oids = list_make1_oid(rel_oid);
+ *vacrels = lappend(*vacrels, relinfo);
+ ++oid_count;
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +540,127 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ return oid_count;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = (VacuumRelation *) lfirst(relation_lc);
+ ListCell *oid;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ foreach(oid, relation->oids)
+ {
+ Relation rel;
+ ListCell *column_lc;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(lfirst_oid(oid), NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+
+ relation_close(rel, NoLock);
+ }
+ }
+}
+
+/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static List *
+dedupe_relations(List *relations)
+{
+ int i = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (relations == NIL) /* nothing to do */
+ return relations;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst(lc);
+ int j;
+
+ /* remove any duplicates in the column list */
+ relation->va_cols = list_concat_unique(NIL, relation->va_cols);
+
+ for (j = ++i; j < list_length(relations); ++j)
+ {
+ VacuumRelation *nth_rel = list_nth_node(VacuumRelation, relations, j);
+
+ /* if already processed or not equal, skip */
+ if (list_member_int(duplicates, j) || !equal(relation->oids, nth_rel->oids))
+ continue;
+
+ /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+ duplicates = lappend_int(duplicates, j);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return relations;
+
+ for (i = 0; i < list_length(relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(VacuumRelation, relations, i));
+ }
+
+ return temp_relations;
}
/*
@@ -1309,6 +1496,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7204169..cd81645 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3764,8 +3764,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_NODE_FIELD(oids);
return newnode;
}
@@ -5212,6 +5223,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..36baf25 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_NODE_FIELD(oids);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..d1f771a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10128,11 +10130,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10142,9 +10143,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10156,24 +10156,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10207,19 +10203,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10246,6 +10240,21 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ VacuumRelation *n = makeNode(VacuumRelation);
+ n->relation = $1;
+ n->va_cols = $2;
+ $$ = (Node *) n;
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..8257271 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3070,6 +3070,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ VacuumRelation *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3081,8 +3082,12 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* 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);
+ rel = makeNode(VacuumRelation);
+ rel->relation = &rangevar;
+ if (OidIsValid(tab->at_relid))
+ rel->oids = list_make1_oid(tab->at_relid);
+
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..b9ada88 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ List *oids; /* corresponding OIDs (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..d1b82ce 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,36 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..1134a58 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,31 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Thu, Aug 24, 2017 at 12:38 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 8/18/17, 12:56 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
According to the docs, VACUUM and ANALYZE "do not support recursing over
inheritance hierarchies" [1]. However, we need a list of OIDs for
partitioned tables. Namely, this piece of code in get_rel_oids(...):if (include_parts)
oid_list = list_concat(oid_list,
find_all_inheritors(relid, NoLock, NULL));
else
oid_list = lappend_oid(oid_list, relid);Since all of these OIDs should correspond to the same partitioned table,
it makes sense to me to leave them together instead of breaking out each
partition into a VacuumRelation. If we did so, I think we would also need
to duplicate the va_cols list for each partition. What do you think?
Robert, Amit and other folks working on extending the existing
partitioning facility would be in better position to answer that, but
I would think that we should have something as flexible as possible,
and storing a list of relation OID in each VacuumRelation makes it
harder to track the uniqueness of relations vacuumed. I agree that the
concept of a partition with multiple parents induces a lot of
problems. But the patch as proposed worries me as it complicates
vacuum() with a double loop: one for each relation vacuumed, and one
inside it with the list of OIDs. Modules calling vacuum() could also
use flexibility, being able to analyze a custom list of columns for
each relation has value as well.
- * relid, if not InvalidOid, indicate the relation to process; otherwise, - * the RangeVar is used. (The latter must always be passed, because it's - * used for error messages.) + * If we intend to process all relations, the 'relations' argument may be + * NIL. This comment actually applies to RelationAndColumns. If the OID is invalid, then RangeVar is used, and should always be set. You are breaking that promise actually for autovacuum. The comment here should say that if relations is NIL all the relations of the database are processes, and for an ANALYZE all the columns are done.Makes sense, I've tried to make this comment clearer.
+ * relations is a list of VacuumRelations to process. If it is NIL, all
+ * relations in the database are processed.
Typo here, VacuumRelation is singular.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 8/23/17, 11:59 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Robert, Amit and other folks working on extending the existing
partitioning facility would be in better position to answer that, but
I would think that we should have something as flexible as possible,
and storing a list of relation OID in each VacuumRelation makes it
harder to track the uniqueness of relations vacuumed. I agree that the
concept of a partition with multiple parents induces a lot of
problems. But the patch as proposed worries me as it complicates
vacuum() with a double loop: one for each relation vacuumed, and one
inside it with the list of OIDs. Modules calling vacuum() could also
use flexibility, being able to analyze a custom list of columns for
each relation has value as well.
I understand your concern, and I'll look into this for v9. I think the
majority of this change will go into get_rel_oids(...). Like you, I am
also curious to what the partitioning folks think.
+ * relations is a list of VacuumRelations to process. If it is NIL, all + * relations in the database are processed. Typo here, VacuumRelation is singular.
I'll make this change in v9.
I should also note that the dedupe_relations(...) function needs another
small fix for column lists. Since the lack of a column list means that we
should ANALYZE all columns, a duplicate table name with an empty column
list should effectively null out any other specified columns. For example,
"ANALYZE table (a, b), table;" currently dedupes to the equivalent of
"ANALYZE table (a, b);" when it should dedupe to "ANALYZE table;".
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 24, 2017 at 11:28 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
I should also note that the dedupe_relations(...) function needs another
small fix for column lists. Since the lack of a column list means that we
should ANALYZE all columns, a duplicate table name with an empty column
list should effectively null out any other specified columns. For example,
"ANALYZE table (a, b), table;" currently dedupes to the equivalent of
"ANALYZE table (a, b);" when it should dedupe to "ANALYZE table;".
This makes me think that it could be a good idea to revisit this bit
in a separate patch. ANALYZE fails as well now when the same column is
defined multiple times with an incomprehensible error message.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 24, 2017 at 12:59 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
Robert, Amit and other folks working on extending the existing
partitioning facility would be in better position to answer that, but
I would think that we should have something as flexible as possible,
and storing a list of relation OID in each VacuumRelation makes it
harder to track the uniqueness of relations vacuumed. I agree that the
concept of a partition with multiple parents induces a lot of
problems. But the patch as proposed worries me as it complicates
vacuum() with a double loop: one for each relation vacuumed, and one
inside it with the list of OIDs. Modules calling vacuum() could also
use flexibility, being able to analyze a custom list of columns for
each relation has value as well.
So ... why have a double loop? I mean, you could just expand this out
to one entry per relation actually being vacuumed, couldn't you?
What happens if you say VACUUM partitioned_table (a), some_partition (b)?
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ foreach(lc, relations)
+ temp_relations = lappend(temp_relations, copyObject(lfirst(lc)));
+ MemoryContextSwitchTo(oldcontext);
+ relations = temp_relations;
Can't we just copyObject() on the whole list?
- ListCell *cur;
-
Why change this? Generally, declaring a separate variable in an inner
scope seems like better style than reusing one that happens to be
lying around in the outer scope.
+ VacuumRelation *relinfo = (VacuumRelation *) lfirst(lc);
Could use lfirst_node.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Aug 26, 2017 at 8:00 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Aug 24, 2017 at 12:59 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:Robert, Amit and other folks working on extending the existing
partitioning facility would be in better position to answer that, but
I would think that we should have something as flexible as possible,
and storing a list of relation OID in each VacuumRelation makes it
harder to track the uniqueness of relations vacuumed. I agree that the
concept of a partition with multiple parents induces a lot of
problems. But the patch as proposed worries me as it complicates
vacuum() with a double loop: one for each relation vacuumed, and one
inside it with the list of OIDs. Modules calling vacuum() could also
use flexibility, being able to analyze a custom list of columns for
each relation has value as well.So ... why have a double loop? I mean, you could just expand this out
to one entry per relation actually being vacuumed, couldn't you?
Yes, if I understand that correctly. That's the point I am exactly
coming at. My suggestion is to have one VacuumRelation entry per
relation vacuumed, even for partitioned tables, and copy the list of
columns to each one.
+ oldcontext = MemoryContextSwitchTo(vac_context); + foreach(lc, relations) + temp_relations = lappend(temp_relations, copyObject(lfirst(lc))); + MemoryContextSwitchTo(oldcontext); + relations = temp_relations;Can't we just copyObject() on the whole list?
Yup.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 8/23/17, 11:59 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+ * relations is a list of VacuumRelations to process. If it is NIL, all + * relations in the database are processed. Typo here, VacuumRelation is singular.
This should be fixed in v9.
On 8/24/17, 5:45 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
This makes me think that it could be a good idea to revisit this bit
in a separate patch. ANALYZE fails as well now when the same column is
defined multiple times with an incomprehensible error message.
The de-duplication code is now in a separate patch,
dedupe_vacuum_relations_v1.patch. I believe it fixes the incomprehensible
error message you were experiencing, but please let me know if you are
still hitting it.
On 8/25/17, 6:00 PM, "Robert Haas" <robertmhaas@gmail.com> wrote:
+ oldcontext = MemoryContextSwitchTo(vac_context); + foreach(lc, relations) + temp_relations = lappend(temp_relations, copyObject(lfirst(lc))); + MemoryContextSwitchTo(oldcontext); + relations = temp_relations;Can't we just copyObject() on the whole list?
I've made this change.
- ListCell *cur;
-Why change this? Generally, declaring a separate variable in an inner
scope seems like better style than reusing one that happens to be
lying around in the outer scope.
I've removed this change.
+ VacuumRelation *relinfo = (VacuumRelation *) lfirst(lc);
Could use lfirst_node.
Done.
On 8/28/17, 5:28 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Yes, if I understand that correctly. That's the point I am exactly
coming at. My suggestion is to have one VacuumRelation entry per
relation vacuumed, even for partitioned tables, and copy the list of
columns to each one.
I've made this change in v9. It does clean up the patch quite a bit.
Nathan
Attachments:
dedupe_vacuum_relations_v1.patchapplication/octet-stream; name=dedupe_vacuum_relations_v1.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c54eea7..a9ad684 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -70,6 +70,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
static void check_columns_exist(List *relations);
+static void dedupe_relations(List **relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -257,6 +258,17 @@ vacuum(int options, List *relations, VacuumParams *params,
check_columns_exist(relations);
/*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same OIDs. For
+ * example, "foo" and "public.foo" could be the same relation.
+ */
+ dedupe_relations(&relations);
+
+ /*
* Decide whether we need to start/commit our own transactions.
*
* For VACUUM (with or without ANALYZE): always do so, so that we can
@@ -598,6 +610,81 @@ check_columns_exist(List *relations)
}
/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static void
+dedupe_relations(List **relations)
+{
+ int i;
+ int next_elem_index = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (*relations == NIL) /* nothing to do */
+ return;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, *relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+
+ ++next_elem_index;
+
+ /*
+ * If this one has already been marked as a duplicate, it has
+ * already been processed, and there's nothing left to do.
+ */
+ if (list_member_int(duplicates, next_elem_index - 1))
+ continue;
+
+ /* remove any duplicates in the column list */
+ relation->va_cols = list_concat_unique(NIL, relation->va_cols);
+
+ /* now look for any duplicates in the remainder of the list */
+ for (i = next_elem_index; i < list_length(*relations); ++i)
+ {
+ VacuumRelation *nth_rel = list_nth_node(VacuumRelation, *relations, i);
+
+ /* if already procesed or not equal, skip */
+ if (list_member_int(duplicates, i) || relation->oid != nth_rel->oid)
+ continue;
+
+ /*
+ * For ANALYZE, if a specified relation has an empty
+ * column list, it means that all columns should be
+ * analyzed. Therefore, any time a relation is
+ * specified with an empty column list, it overrides any
+ * other column lists specified for that relation.
+ */
+ if (relation->va_cols == NIL || nth_rel->va_cols == NIL)
+ relation->va_cols = NIL;
+ else /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+
+ duplicates = lappend_int(duplicates, i);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return;
+
+ for (i = 0; i < list_length(*relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(VacuumRelation, *relations, i));
+ }
+
+ *relations = temp_relations;
+}
+
+/*
* vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
*
* The output parameters are:
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 97a328a..d1b82ce 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -117,6 +117,7 @@ ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 991bea7..1134a58 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -93,6 +93,7 @@ ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
vacuum_multiple_tables_v9.patchapplication/octet-stream; name=vacuum_multiple_tables_v9.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..9843658 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ] [, ...]
</synopsis>
</refsynopsisdiv>
@@ -38,10 +38,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,
- in which case only the statistics for those columns are collected.
+ With no parameters, <command>ANALYZE</command> examines every table in the
+ current database. With parameters, <command>ANALYZE</command> examines
+ only the tables specified. It is further possible to give a list of
+ column names for each table, in which case only the statistics for those
+ columns are collected.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..d4d9eaa 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>
@@ -40,9 +40,10 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
</para>
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
<para>
@@ -165,7 +166,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fbad13e..79966746 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..c54eea7 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "parser/parse_relation.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 void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +92,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +120,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +128,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +143,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +194,18 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided")));
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +236,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +322,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +343,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +398,91 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,6 +500,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +515,19 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ relinfo = makeNode(VacuumRelation);
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo->oid = rel_oid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +535,66 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1430,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7204169..9cb2593 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3764,8 +3764,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5212,6 +5223,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..508b7c9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..d1f771a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10128,11 +10130,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10142,9 +10143,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10156,24 +10156,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10207,19 +10203,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10246,6 +10240,21 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ VacuumRelation *n = makeNode(VacuumRelation);
+ n->relation = $1;
+ n->va_cols = $2;
+ $$ = (Node *) n;
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..b124e4b 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3070,6 +3070,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ VacuumRelation *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3081,8 +3082,11 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* 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);
+ rel = makeNode(VacuumRelation);
+ rel->relation = &rangevar;
+ rel->oid = tab->at_relid;
+
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..118df0b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..97a328a 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,35 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..991bea7 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Sat, Aug 26, 2017 at 8:00 AM, Robert Haas <robertmhaas@gmail.com> wrote:
What happens if you say VACUUM partitioned_table (a), some_partition (b)?
Using v9, if you do that:
=# CREATE TABLE parent (id int) PARTITION BY RANGE (id);
CREATE TABLE
=# CREATE TABLE child_1_10001 partition of parent for values from (1)
to (10001);
CREATE TABLE
=# CREATE TABLE child_10001_20001 partition of parent for values from
(10001) to (20001);
CREATE TABLE
=# insert into parent values (generate_series(1,20000));
INSERT 0 20000
Vacuuming the parent vacuums all the children, so any child listed
would get vacuumed twice, still this does not cause an error:
=# vacuum parent, child_10001_20000;
VACUUM
And with the de-duplication patch on top of it, things are vacuumed only once.
On Tue, Aug 29, 2017 at 7:56 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 8/23/17, 11:59 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+ * relations is a list of VacuumRelations to process. If it is NIL, all + * relations in the database are processed. Typo here, VacuumRelation is singular.This should be fixed in v9.
On 8/24/17, 5:45 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
This makes me think that it could be a good idea to revisit this bit
in a separate patch. ANALYZE fails as well now when the same column is
defined multiple times with an incomprehensible error message.The de-duplication code is now in a separate patch,
dedupe_vacuum_relations_v1.patch. I believe it fixes the incomprehensible
error message you were experiencing, but please let me know if you are
still hitting it.
It looks that problems in this area are fixed using the second patch.
On 8/25/17, 6:00 PM, "Robert Haas" <robertmhaas@gmail.com> wrote:
+ oldcontext = MemoryContextSwitchTo(vac_context); + foreach(lc, relations) + temp_relations = lappend(temp_relations, copyObject(lfirst(lc))); + MemoryContextSwitchTo(oldcontext); + relations = temp_relations;Can't we just copyObject() on the whole list?
I've made this change.
- ListCell *cur;
-Why change this? Generally, declaring a separate variable in an inner
scope seems like better style than reusing one that happens to be
lying around in the outer scope.I've removed this change.
+ VacuumRelation *relinfo = (VacuumRelation *) lfirst(lc);
Could use lfirst_node.
Done.
On 8/28/17, 5:28 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Yes, if I understand that correctly. That's the point I am exactly
coming at. My suggestion is to have one VacuumRelation entry per
relation vacuumed, even for partitioned tables, and copy the list of
columns to each one.I've made this change in v9. It does clean up the patch quite a bit.
Here is some input for vacuum_multiple_tables_v9, about which I think
that we are getting to something committable. Here are some minor
comments.
<para>
- With no parameter, <command>VACUUM</command> processes every table in the
+ With no parameters, <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.
+ With parameters, <command>VACUUM</command> processes only the tables
+ specified.
</para>
The part about parameters looks fine to me if unchanged.
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a
column list is provided")));
+ }
Could you add a hint with the relation name involved here? When many
relations are defined in the VACUUM query this would be useful for the
user.
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
It pains me to see that get_rel_relkind does not return an error if
the relation is missing, we could use it here. I would welcome a
refactoring with a missing_ok argument a lot! Now this patch for
VACUUM does not justify breaking potentially many extensions...
+ relinfo = makeNode(VacuumRelation);
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo->oid = rel_oid;
There are 4 patterns like that in the patch. We could make use of a
makeVacuumRelation.
About the de-duplication patch, I have to admit that I am still not a
fan of doing such a thing. Another road that we could take is to
simply complain with a proper error message if:
- the same column name is specified twice for a relation.
- the same relation is defined twice. In the case of partitions, we
could track the fact that it is already listed as part of a parent,
though perhaps it does not seem worth the extra CPU cost especially
when there are multiple nesting levels with partitions.
Autovacuum has also the advantage, if I recall correctly, to select
all columns for analyze, and skip parent partitions when scanning for
relations so that's a safe bet from this side. Opinions welcome.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 8/28/17, 11:26 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Here is some input for vacuum_multiple_tables_v9, about which I think
that we are getting to something committable. Here are some minor
comments.
Thanks for another review.
<para> - With no parameter, <command>VACUUM</command> processes every table in the + With no parameters, <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. + With parameters, <command>VACUUM</command> processes only the tables + specified. </para> The part about parameters looks fine to me if unchanged.
This is reverted in v10.
+ foreach(lc, relations) + { + VacuumRelation *relation = lfirst_node(VacuumRelation, lc); + if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ANALYZE option must be specified when a column list is provided"))); + } Could you add a hint with the relation name involved here? When many relations are defined in the VACUUM query this would be useful for the user.
Added in v10.
+ relinfo = makeNode(VacuumRelation); + rel_oid = HeapTupleGetOid(tuple); + relinfo->oid = rel_oid; There are 4 patterns like that in the patch. We could make use of a makeVacuumRelation.
Agreed, I've added one.
About the de-duplication patch, I have to admit that I am still not a
fan of doing such a thing. Another road that we could take is to
simply complain with a proper error message if:
- the same column name is specified twice for a relation.
- the same relation is defined twice. In the case of partitions, we
could track the fact that it is already listed as part of a parent,
though perhaps it does not seem worth the extra CPU cost especially
when there are multiple nesting levels with partitions.
Autovacuum has also the advantage, if I recall correctly, to select
all columns for analyze, and skip parent partitions when scanning for
relations so that's a safe bet from this side. Opinions welcome.
I lean towards favoring the de-duplication patch, but maybe I am biased
as the author. I can see the following advantages:
1. Ease of use. By taking care of de-duplicating on behalf of the user,
they needn't worry about inheritance structures or accidentally
specifying the same relation or column twice. This might be especially
useful if a large number of relations or columns must be specified.
2. Resource conservation. By de-duplicating, VACUUM and ANALYZE are
doing roughly the same thing but with less work.
3. The obnoxious errors you were experiencing are resolved. This seems
like the strongest argument to me, as it fixes an existing issue.
Disadvantages might include:
1. Users cannot schedule repeated VACUUMs on the same relation (e.g.
'VACUUM table, table, table;'). However, I cannot think of a time when
I needed this, and it seems like something else is wrong with VACUUM if
folks are resorting to this. In the end, you could still achieve this
via several VACUUM statements.
2. Any inferred ordering for how the relations are processed will not
be accurate if there are duplicates. Ultimately, users might lose some
amount of control here, but I am not sure how prevalent this use case
might be. In the worst case, you could achieve this via several
individual VACUUM statements as well.
Your suggestion to ERROR seems like a reasonable compromise, but I
could see it causing frustration in some cases, especially with
partitioning.
Nathan
Attachments:
dedupe_vacuum_relations_v2.patchapplication/octet-stream; name=dedupe_vacuum_relations_v2.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 56ea11d..feaa4dc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -70,6 +70,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
static void check_columns_exist(List *relations);
+static void dedupe_relations(List **relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -268,6 +269,17 @@ vacuum(int options, List *relations, VacuumParams *params,
check_columns_exist(relations);
/*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same OIDs. For
+ * example, "foo" and "public.foo" could be the same relation.
+ */
+ dedupe_relations(&relations);
+
+ /*
* Decide whether we need to start/commit our own transactions.
*
* For VACUUM (with or without ANALYZE): always do so, so that we can
@@ -621,6 +633,81 @@ check_columns_exist(List *relations)
}
/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static void
+dedupe_relations(List **relations)
+{
+ int i;
+ int next_elem_index = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (*relations == NIL) /* nothing to do */
+ return;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, *relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+
+ ++next_elem_index;
+
+ /*
+ * If this one has already been marked as a duplicate, it has
+ * already been processed, and there's nothing left to do.
+ */
+ if (list_member_int(duplicates, next_elem_index - 1))
+ continue;
+
+ /* remove any duplicates in the column list */
+ relation->va_cols = list_concat_unique(NIL, relation->va_cols);
+
+ /* now look for any duplicates in the remainder of the list */
+ for (i = next_elem_index; i < list_length(*relations); ++i)
+ {
+ VacuumRelation *nth_rel = list_nth_node(VacuumRelation, *relations, i);
+
+ /* if already procesed or not equal, skip */
+ if (list_member_int(duplicates, i) || relation->oid != nth_rel->oid)
+ continue;
+
+ /*
+ * For ANALYZE, if a specified relation has an empty
+ * column list, it means that all columns should be
+ * analyzed. Therefore, any time a relation is
+ * specified with an empty column list, it overrides any
+ * other column lists specified for that relation.
+ */
+ if (relation->va_cols == NIL || nth_rel->va_cols == NIL)
+ relation->va_cols = NIL;
+ else /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+
+ duplicates = lappend_int(duplicates, i);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return;
+
+ for (i = 0; i < list_length(*relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(VacuumRelation, *relations, i));
+ }
+
+ *relations = temp_relations;
+}
+
+/*
* vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
*
* The output parameters are:
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d29dd8f..113f59c 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -120,6 +120,7 @@ ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 991bea7..1134a58 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -93,6 +93,7 @@ ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
vacuum_multiple_tables_v10.patchapplication/octet-stream; name=vacuum_multiple_tables_v10.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..0f4f1c0 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ] [, ...]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..00ec6bc 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>
@@ -165,7 +165,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fbad13e..79966746 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..56ea11d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "parser/parse_relation.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 void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +92,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +120,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +128,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +143,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +194,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +247,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +333,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +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(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +409,104 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Helper function for generating a new VacuumRelation node.
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *vacrel = makeNode(VacuumRelation);
+ vacrel->relation = relation;
+ vacrel->va_cols = va_cols;
+ vacrel->oid = oid;
+ return vacrel;
+}
+
+/*
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,6 +524,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +539,18 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo = makeVacuumRelation(NULL, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +558,66 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1453,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7204169..9cb2593 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3764,8 +3764,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5212,6 +5223,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..508b7c9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..ca168ea 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -54,6 +54,7 @@
#include "catalog/pg_trigger.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
+#include "commands/vacuum.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/gramparse.h"
@@ -366,6 +367,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +398,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10128,11 +10131,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10142,9 +10144,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10156,24 +10157,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10207,19 +10204,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10246,6 +10241,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..5c6457d 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3070,6 +3070,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ VacuumRelation *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3081,8 +3082,8 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* 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);
+ rel = makeVacuumRelation(&rangevar, NIL, tab->at_relid);
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..c13f3b0 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
@@ -186,6 +185,7 @@ extern void vacuum_set_xid_limits(Relation rel,
MultiXactId *mxactFullScanLimit);
extern void vac_update_datfrozenxid(void);
extern void vacuum_delay_point(void);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
/* in commands/vacuumlazy.c */
extern void lazy_vacuum_rel(Relation onerel, int options,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..118df0b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..d29dd8f 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,38 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..991bea7 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Wed, Aug 30, 2017 at 1:47 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 8/28/17, 11:26 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
About the de-duplication patch, I have to admit that I am still not a
fan of doing such a thing. Another road that we could take is to
simply complain with a proper error message if:
- the same column name is specified twice for a relation.
- the same relation is defined twice. In the case of partitions, we
could track the fact that it is already listed as part of a parent,
though perhaps it does not seem worth the extra CPU cost especially
when there are multiple nesting levels with partitions.
Autovacuum has also the advantage, if I recall correctly, to select
all columns for analyze, and skip parent partitions when scanning for
relations so that's a safe bet from this side. Opinions welcome.I lean towards favoring the de-duplication patch, but maybe I am biased
as the author.
I can be biased as reviewer then.
I can see the following advantages:
1. Ease of use. By taking care of de-duplicating on behalf of the user,
they needn't worry about inheritance structures or accidentally
specifying the same relation or column twice. This might be especially
useful if a large number of relations or columns must be specified.
2. Resource conservation. By de-duplicating, VACUUM and ANALYZE are
doing roughly the same thing but with less work.
3. The obnoxious errors you were experiencing are resolved. This seems
like the strongest argument to me, as it fixes an existing issue.Disadvantages might include:
1. Users cannot schedule repeated VACUUMs on the same relation (e.g.
'VACUUM table, table, table;'). However, I cannot think of a time when
I needed this, and it seems like something else is wrong with VACUUM if
folks are resorting to this. In the end, you could still achieve this
via several VACUUM statements.
2. Any inferred ordering for how the relations are processed will not
be accurate if there are duplicates. Ultimately, users might lose some
amount of control here, but I am not sure how prevalent this use case
might be. In the worst case, you could achieve this via several
individual VACUUM statements as well.Your suggestion to ERROR seems like a reasonable compromise, but I
could see it causing frustration in some cases, especially with
partitioning.
Yeah... Each approach has its cost and its advantages. It may be
better to wait for more opinions, no many people have complained yet
that for example a list of columns using twice the same one fails.
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable
class="PARAMETER">table_name</replaceable> ] [, ...]
I just noticed that... But regarding the docs, I think that you have
misplaced the position of "[, ...]", which should be inside the
table_name portion in the case of what I quote here, no?
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *vacrel = makeNode(VacuumRelation);
+ vacrel->relation = relation;
+ vacrel->va_cols = va_cols;
+ vacrel->oid = oid;
+ return vacrel;
+}
Perhaps in makefuncs.c instead of vacuum.c? That's usually the place
used for node constructions like that.
Those are minor tweaks, I'll be fine to move that as ready for
committer after for those points are addressed for the main patch.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 8/30/17, 5:37 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Yeah... Each approach has its cost and its advantages. It may be
better to wait for more opinions, no many people have complained yet
that for example a list of columns using twice the same one fails.
Sounds good to me.
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable
class="PARAMETER">table_name</replaceable> ] [, ...]
I just noticed that... But regarding the docs, I think that you have
misplaced the position of "[, ...]", which should be inside the
table_name portion in the case of what I quote here, no?
I think that's what I had initially, but it was changed somewhere along
the line. It is a little more complicated for the versions that accept
column lists.
VACUUM ... ANALYZE [ [ table_name [ (column_name [, ...] ) ] ] [, ...] ]
ISTM that we need the extra brackets here to clarify that the table and
column list combination is what can be provided in a list. Does that
make sense? Or do you think we can omit the outermost brackets here?
+VacuumRelation * +makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid) +{ + VacuumRelation *vacrel = makeNode(VacuumRelation); + vacrel->relation = relation; + vacrel->va_cols = va_cols; + vacrel->oid = oid; + return vacrel; +} Perhaps in makefuncs.c instead of vacuum.c? That's usually the place used for node constructions like that.
Ah, yes. That is a much better place. I'll make this change.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Aug 30, 2017 at 4:08 PM, Bossart, Nathan <bossartn@amazon.com>
wrote:
On 8/30/17, 5:37 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Yeah... Each approach has its cost and its advantages. It may be
better to wait for more opinions, no many people have complained yet
that for example a list of columns using twice the same one fails.Sounds good to me.
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable
class="PARAMETER">table_name</replaceable> ] [, ...]
I just noticed that... But regarding the docs, I think that you have
misplaced the position of "[, ...]", which should be inside the
table_name portion in the case of what I quote here, no?I think that's what I had initially, but it was changed somewhere along
the line. It is a little more complicated for the versions that accept
column lists.VACUUM ... ANALYZE [ [ table_name [ (column_name [, ...] ) ] ] [, ...] ]
ISTM that we need the extra brackets here to clarify that the table and
column list combination is what can be provided in a list. Does that
make sense? Or do you think we can omit the outermost brackets here?
Inspired by the syntax documentation for EXPLAIN:
VACUUM [ ( option [, ...] ) ] [ table_def [, ...] ]
where option can be one of:
FULL
FREEZE
VERBOSE
DISABLE_PAGE_SKIPPING
and where table_def is:
table_name [ ( column_name [, ... ] ) ]
David J.
On Thu, Aug 31, 2017 at 8:35 AM, David G. Johnston
<david.g.johnston@gmail.com> wrote:
Inspired by the syntax documentation for EXPLAIN:
VACUUM [ ( option [, ...] ) ] [ table_def [, ...] ]
where option can be one of:
FULL
FREEZE
VERBOSE
DISABLE_PAGE_SKIPPINGand where table_def is:
table_name [ ( column_name [, ... ] ) ]
Yes, splitting things would be nice with the column list. I need more coffee.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 8/30/17, 5:37 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+VacuumRelation * +makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid) +{ + VacuumRelation *vacrel = makeNode(VacuumRelation); + vacrel->relation = relation; + vacrel->va_cols = va_cols; + vacrel->oid = oid; + return vacrel; +} Perhaps in makefuncs.c instead of vacuum.c? That's usually the place used for node constructions like that.
This function is moved in v11.
On 8/30/17, 6:52 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
On Thu, Aug 31, 2017 at 8:35 AM, David G. Johnston
<david.g.johnston@gmail.com> wrote:Inspired by the syntax documentation for EXPLAIN:
VACUUM [ ( option [, ...] ) ] [ table_def [, ...] ]
where option can be one of:
FULL
FREEZE
VERBOSE
DISABLE_PAGE_SKIPPINGand where table_def is:
table_name [ ( column_name [, ... ] ) ]Yes, splitting things would be nice with the column list. I need more coffee.
I've made this change in v11 as well.
v2 of the de-duplication patch seems to still apply cleanly, so I haven't
made any further changes to it.
Nathan
Attachments:
dedupe_vacuum_relations_v2.patchapplication/octet-stream; name=dedupe_vacuum_relations_v2.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 56ea11d..feaa4dc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -70,6 +70,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
static void check_columns_exist(List *relations);
+static void dedupe_relations(List **relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -268,6 +269,17 @@ vacuum(int options, List *relations, VacuumParams *params,
check_columns_exist(relations);
/*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same OIDs. For
+ * example, "foo" and "public.foo" could be the same relation.
+ */
+ dedupe_relations(&relations);
+
+ /*
* Decide whether we need to start/commit our own transactions.
*
* For VACUUM (with or without ANALYZE): always do so, so that we can
@@ -621,6 +633,81 @@ check_columns_exist(List *relations)
}
/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static void
+dedupe_relations(List **relations)
+{
+ int i;
+ int next_elem_index = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (*relations == NIL) /* nothing to do */
+ return;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, *relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+
+ ++next_elem_index;
+
+ /*
+ * If this one has already been marked as a duplicate, it has
+ * already been processed, and there's nothing left to do.
+ */
+ if (list_member_int(duplicates, next_elem_index - 1))
+ continue;
+
+ /* remove any duplicates in the column list */
+ relation->va_cols = list_concat_unique(NIL, relation->va_cols);
+
+ /* now look for any duplicates in the remainder of the list */
+ for (i = next_elem_index; i < list_length(*relations); ++i)
+ {
+ VacuumRelation *nth_rel = list_nth_node(VacuumRelation, *relations, i);
+
+ /* if already procesed or not equal, skip */
+ if (list_member_int(duplicates, i) || relation->oid != nth_rel->oid)
+ continue;
+
+ /*
+ * For ANALYZE, if a specified relation has an empty
+ * column list, it means that all columns should be
+ * analyzed. Therefore, any time a relation is
+ * specified with an empty column list, it overrides any
+ * other column lists specified for that relation.
+ */
+ if (relation->va_cols == NIL || nth_rel->va_cols == NIL)
+ relation->va_cols = NIL;
+ else /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+
+ duplicates = lappend_int(duplicates, i);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return;
+
+ for (i = 0; i < list_length(*relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(VacuumRelation, *relations, i));
+ }
+
+ *relations = temp_relations;
+}
+
+/*
* vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
*
* The output parameters are:
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d29dd8f..113f59c 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -120,6 +120,7 @@ ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 991bea7..1134a58 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -93,6 +93,7 @@ ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
vacuum_multiple_tables_v11.patchapplication/octet-stream; name=vacuum_multiple_tables_v11.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee101df..c69a3490bf 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d117..3a0afa3b74 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fbad13ea94..7996674630 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa181207a..a9fe412351 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +69,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +195,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +248,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +334,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +355,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +410,91 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,6 +512,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +527,18 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo = makeVacuumRelation(NULL, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +546,66 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1441,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f9ddf4ed76..a655a33876 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3766,8 +3766,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5214,6 +5225,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03633..508b7c95a3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039da9..60d6addf0f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99baf..5b4e09e4fa 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10128,11 +10130,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10142,9 +10143,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10156,24 +10156,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10207,19 +10203,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10246,6 +10240,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0a9d..43bf1144ee 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3070,6 +3071,7 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ VacuumRelation *rel;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
@@ -3081,8 +3083,8 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
/* 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);
+ rel = makeVacuumRelation(&rangevar, NIL, tab->at_relid);
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a9035112e9..7a7b793ddf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1817..12d78ff923 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3363..ffeeb4919b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a75da..118df0b95a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663087..d29dd8f3a3 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,38 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04917..991bea7752 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Thu, Aug 31, 2017 at 10:52 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 8/30/17, 5:37 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+VacuumRelation * +makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid) +{ + VacuumRelation *vacrel = makeNode(VacuumRelation); + vacrel->relation = relation; + vacrel->va_cols = va_cols; + vacrel->oid = oid; + return vacrel; +} Perhaps in makefuncs.c instead of vacuum.c? That's usually the place used for node constructions like that.This function is moved in v11.
On 8/30/17, 6:52 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
On Thu, Aug 31, 2017 at 8:35 AM, David G. Johnston
<david.g.johnston@gmail.com> wrote:Inspired by the syntax documentation for EXPLAIN:
VACUUM [ ( option [, ...] ) ] [ table_def [, ...] ]
where option can be one of:
FULL
FREEZE
VERBOSE
DISABLE_PAGE_SKIPPINGand where table_def is:
table_name [ ( column_name [, ... ] ) ]Yes, splitting things would be nice with the column list. I need more coffee.
I've made this change in v11 as well.
v2 of the de-duplication patch seems to still apply cleanly, so I haven't
made any further changes to it.
I reviewed these patches and found a issue.
autovacuum worker seems not to work fine. I got an error message;
ERROR: unrecognized node type: 0
CONTEXT: automatic analyze of table "postgres.public.hoge"
I think we should set T_RangeVar to rangevar.type in
autovacuum_do_vac_analyze function.
Also, there is a small typo in dedupe_vacuum_relations_v2.patch.
+ /* if already procesed or not equal, skip */
+ if (list_member_int(duplicates, i) ||
relation->oid != nth_rel->oid)
+ continue;
s/procesed/processed/g
Regards,
--
Masahiko Sawada
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 8/31/17, 2:24 AM, "Masahiko Sawada" <sawada.mshk@gmail.com> wrote:
I reviewed these patches and found a issue.
Thanks for reviewing.
autovacuum worker seems not to work fine. I got an error message;
ERROR: unrecognized node type: 0
CONTEXT: automatic analyze of table "postgres.public.hoge"I think we should set T_RangeVar to rangevar.type in
autovacuum_do_vac_analyze function.
Yes, it looks like the NodeTag is not getting set on the RangeVar.
I went ahead and switched this to makeRangeVar(...) instead of
keeping it manually allocated on the stack. Autovacuum seems to be
working as usual now.
Also, there is a small typo in dedupe_vacuum_relations_v2.patch.
+ /* if already procesed or not equal, skip */ + if (list_member_int(duplicates, i) || relation->oid != nth_rel->oid) + continue;s/procesed/processed/g
This should be fixed in v3.
Nathan
Attachments:
vacuum_multiple_tables_v12.patchapplication/octet-stream; name=vacuum_multiple_tables_v12.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee101df..c69a3490bf 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d117..3a0afa3b74 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fbad13ea94..7996674630 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa181207a..a9fe412351 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +69,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +195,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +248,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +334,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +355,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +410,91 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,6 +512,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +527,18 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo = makeVacuumRelation(NULL, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +546,66 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1441,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f9ddf4ed76..a655a33876 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3766,8 +3766,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5214,6 +5225,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03633..508b7c95a3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039da9..60d6addf0f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99baf..5b4e09e4fa 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10128,11 +10130,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10142,9 +10143,8 @@ 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;
- $$ = (Node *)n;
+ n->rels = $5;
+ $$ = (Node *) n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
@@ -10156,24 +10156,20 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- $$ = (Node *)n;
+ $$ = (Node *) n;
}
| VACUUM '(' vacuum_option_list ')'
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10207,19 +10203,17 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
- $$ = (Node *)n;
+ n->rels = NIL;
+ $$ = (Node *) n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
- $$ = (Node *)n;
+ n->rels = $3;
+ $$ = (Node *) n;
}
;
@@ -10246,6 +10240,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0a9d..9e4e86ca97 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,15 @@ 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;
/* 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);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ vacuum(tab->at_vacoptions, list_make1(rel), &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a9035112e9..7a7b793ddf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1817..12d78ff923 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3363..ffeeb4919b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a75da..118df0b95a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663087..d29dd8f3a3 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,38 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04917..991bea7752 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
dedupe_vacuum_relations_v3.patchapplication/octet-stream; name=dedupe_vacuum_relations_v3.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a9fe412351..fbc442f1a3 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -71,6 +71,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
static void check_columns_exist(List *relations);
+static void dedupe_relations(List **relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -269,6 +270,17 @@ vacuum(int options, List *relations, VacuumParams *params,
check_columns_exist(relations);
/*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same OIDs. For
+ * example, "foo" and "public.foo" could be the same relation.
+ */
+ dedupe_relations(&relations);
+
+ /*
* Decide whether we need to start/commit our own transactions.
*
* For VACUUM (with or without ANALYZE): always do so, so that we can
@@ -609,6 +621,81 @@ check_columns_exist(List *relations)
}
/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static void
+dedupe_relations(List **relations)
+{
+ int i;
+ int next_elem_index = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (*relations == NIL) /* nothing to do */
+ return;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, *relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+
+ ++next_elem_index;
+
+ /*
+ * If this one has already been marked as a duplicate, it has
+ * already been processed, and there's nothing left to do.
+ */
+ if (list_member_int(duplicates, next_elem_index - 1))
+ continue;
+
+ /* remove any duplicates in the column list */
+ relation->va_cols = list_concat_unique(NIL, relation->va_cols);
+
+ /* now look for any duplicates in the remainder of the list */
+ for (i = next_elem_index; i < list_length(*relations); ++i)
+ {
+ VacuumRelation *nth_rel = list_nth_node(VacuumRelation, *relations, i);
+
+ /* if already processed or not equal, skip */
+ if (list_member_int(duplicates, i) || relation->oid != nth_rel->oid)
+ continue;
+
+ /*
+ * For ANALYZE, if a specified relation has an empty
+ * column list, it means that all columns should be
+ * analyzed. Therefore, any time a relation is
+ * specified with an empty column list, it overrides any
+ * other column lists specified for that relation.
+ */
+ if (relation->va_cols == NIL || nth_rel->va_cols == NIL)
+ relation->va_cols = NIL;
+ else /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+
+ duplicates = lappend_int(duplicates, i);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return;
+
+ for (i = 0; i < list_length(*relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(VacuumRelation, *relations, i));
+ }
+
+ *relations = temp_relations;
+}
+
+/*
* vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
*
* The output parameters are:
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d29dd8f3a3..113f59cfa9 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -120,6 +120,7 @@ ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 991bea7752..1134a58be5 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -93,6 +93,7 @@ ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
On Fri, Sep 1, 2017 at 12:25 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 8/31/17, 2:24 AM, "Masahiko Sawada" <sawada.mshk@gmail.com> wrote:
I reviewed these patches and found a issue.
Thanks for reviewing.
autovacuum worker seems not to work fine. I got an error message;
ERROR: unrecognized node type: 0
CONTEXT: automatic analyze of table "postgres.public.hoge"I think we should set T_RangeVar to rangevar.type in
autovacuum_do_vac_analyze function.Yes, it looks like the NodeTag is not getting set on the RangeVar.
I went ahead and switched this to makeRangeVar(...) instead of
keeping it manually allocated on the stack. Autovacuum seems to be
working as usual now.
Hm. Here is the diff between v11 and v12:
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
- VacuumRelation *rel;
-
- /* 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;
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- rel = makeVacuumRelation(&rangevar, NIL, tab->at_relid);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
But there is this commit in vacuum.c:
* It is the caller's responsibility that all parameters are allocated
in a
* memory context that will not disappear at transaction commit.
And I don't think we want to break that promise as newNode() uses
palloc0fast() which allocates data in the current memory context (see
4873c96f). I think that you had better just use NodeSetTag here and be
done with it. Also, it seems to me that this could be fixed as a
separate patch. It is definitely an incorrect pattern...
- $$ = (Node *)n;
+ $$ = (Node *) n;
Spurious noise. And the coding pattern in gram.y is to not add a space
(make new code look like its surroundings as the documentation says).
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/1/17, 12:11 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Hm. Here is the diff between v11 and v12: static void autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy) { - RangeVar rangevar; - VacuumRelation *rel; - - /* 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;/* Let pgstat know what we're doing */
autovac_report_activity(tab);- rel = makeVacuumRelation(&rangevar, NIL, tab->at_relid); + rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1); + rel = makeVacuumRelation(rangevar, NIL, tab->at_relid); But there is this commit in vacuum.c: * It is the caller's responsibility that all parameters are allocated in a * memory context that will not disappear at transaction commit. And I don't think we want to break that promise as newNode() uses palloc0fast() which allocates data in the current memory context (see 4873c96f). I think that you had better just use NodeSetTag here and be done with it. Also, it seems to me that this could be fixed as a separate patch. It is definitely an incorrect pattern...
Don't we have a similar problem with makeVacuumRelation() and list_make1()?
I went ahead and moved the RangeVar, VacuumRelation, and List into local
variables for now, but I agree that this could be improved in a separate
patch. Perhaps these could be allocated in AutovacMemCxt. I see from
4873c96f that autovacuum_do_vac_analyze() used to allocate the list of OIDs
in that "long-lived" memory context.
- $$ = (Node *)n; + $$ = (Node *) n; Spurious noise. And the coding pattern in gram.y is to not add a space (make new code look like its surroundings as the documentation says).
I've fixed this in v13.
Nathan
Attachments:
dedupe_vacuum_relations_v3.patchapplication/octet-stream; name=dedupe_vacuum_relations_v3.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a9fe412351..fbc442f1a3 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -71,6 +71,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
static void check_columns_exist(List *relations);
+static void dedupe_relations(List **relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -269,6 +270,17 @@ vacuum(int options, List *relations, VacuumParams *params,
check_columns_exist(relations);
/*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same OIDs. For
+ * example, "foo" and "public.foo" could be the same relation.
+ */
+ dedupe_relations(&relations);
+
+ /*
* Decide whether we need to start/commit our own transactions.
*
* For VACUUM (with or without ANALYZE): always do so, so that we can
@@ -609,6 +621,81 @@ check_columns_exist(List *relations)
}
/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static void
+dedupe_relations(List **relations)
+{
+ int i;
+ int next_elem_index = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (*relations == NIL) /* nothing to do */
+ return;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, *relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+
+ ++next_elem_index;
+
+ /*
+ * If this one has already been marked as a duplicate, it has
+ * already been processed, and there's nothing left to do.
+ */
+ if (list_member_int(duplicates, next_elem_index - 1))
+ continue;
+
+ /* remove any duplicates in the column list */
+ relation->va_cols = list_concat_unique(NIL, relation->va_cols);
+
+ /* now look for any duplicates in the remainder of the list */
+ for (i = next_elem_index; i < list_length(*relations); ++i)
+ {
+ VacuumRelation *nth_rel = list_nth_node(VacuumRelation, *relations, i);
+
+ /* if already processed or not equal, skip */
+ if (list_member_int(duplicates, i) || relation->oid != nth_rel->oid)
+ continue;
+
+ /*
+ * For ANALYZE, if a specified relation has an empty
+ * column list, it means that all columns should be
+ * analyzed. Therefore, any time a relation is
+ * specified with an empty column list, it overrides any
+ * other column lists specified for that relation.
+ */
+ if (relation->va_cols == NIL || nth_rel->va_cols == NIL)
+ relation->va_cols = NIL;
+ else /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+
+ duplicates = lappend_int(duplicates, i);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return;
+
+ for (i = 0; i < list_length(*relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(VacuumRelation, *relations, i));
+ }
+
+ *relations = temp_relations;
+}
+
+/*
* vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
*
* The output parameters are:
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d29dd8f3a3..113f59cfa9 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -120,6 +120,7 @@ ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 991bea7752..1134a58be5 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -93,6 +93,7 @@ ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
vacuum_multiple_tables_v13.patchapplication/octet-stream; name=vacuum_multiple_tables_v13.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fbad13e..79966746 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..a9fe412 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +69,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +195,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +248,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +334,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +355,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +410,91 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,6 +512,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +527,18 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo = makeVacuumRelation(NULL, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +546,66 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1441,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f9ddf4e..a655a33 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3766,8 +3766,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5214,6 +5225,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..508b7c9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..d97b507 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10128,11 +10130,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10142,8 +10143,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10162,18 +10162,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10207,18 +10203,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10246,6 +10240,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..da92ebd 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3070,19 +3070,36 @@ static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
RangeVar rangevar;
+ VacuumRelation rel;
+ List rel_list;
+ ListCell lc;
/* Set up command parameters --- use local variables instead of palloc */
MemSet(&rangevar, 0, sizeof(rangevar));
-
+ NodeSetTag(&rangevar, T_RangeVar);
rangevar.schemaname = tab->at_nspname;
rangevar.relname = tab->at_relname;
rangevar.location = -1;
+ MemSet(&rel, 0, sizeof(rel));
+ NodeSetTag(&rel, T_VacuumRelation);
+ rel.relation = &rangevar;
+ rel.va_cols = NIL;
+ rel.oid = tab->at_relid;
+
+ MemSet(&rel_list, 0, sizeof(rel_list));
+ NodeSetTag(&rel_list, T_List);
+ rel_list.length = 1;
+ rel_list.head = &lc;
+ rel_list.tail = &lc;
+
+ MemSet(&lc, 0, sizeof(lc));
+ lfirst(rel_list.head) = &rel;
+
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, &rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..6ecafd1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..d29dd8f 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,38 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..991bea7 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Sat, Sep 2, 2017 at 3:00 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Don't we have a similar problem with makeVacuumRelation() and list_make1()?
Yeah, indeed. I forgot about this portion.
I went ahead and moved the RangeVar, VacuumRelation, and List into local
variables for now, but I agree that this could be improved in a separate
patch. Perhaps these could be allocated in AutovacMemCxt. I see from
4873c96f that autovacuum_do_vac_analyze() used to allocate the list of OIDs
in that "long-lived" memory context.
Indeed, and this has been removed in 9319fd89 by Álvaro as this API
did not need to be this complicated at this point, but now we have to.
I did not consider first that the list portion also needed to be
modified, perhaps because I am not coding that myself... So now that
it is becoming more complicated what about just using AutovacMemCxt?
This would simplify the list of VacuumRelation entries and the
RangeVar creation as well, and honestly this is ugly and there are no
other similar patterns in the backend code:
+ MemSet(&rel_list, 0, sizeof(rel_list));
+ NodeSetTag(&rel_list, T_List);
+ rel_list.length = 1;
+ rel_list.head = &lc;
+ rel_list.tail = &lc;
+
+ MemSet(&lc, 0, sizeof(lc));
+ lfirst(rel_list.head) = &rel;
This would become way more readable by using makeRangeVar() and the
new makeVacuumRelation. As this is partly my fault that we are at this
state, I am fine as well to remove this burden from you, Nathan, and
fix that myself in a new version. And I don't want to step on your
toes either :)
- $$ = (Node *)n; + $$ = (Node *) n; Spurious noise. And the coding pattern in gram.y is to not add a space (make new code look like its surroundings as the documentation says).I've fixed this in v13.
Thanks.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/3/17, 11:46 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
I did not consider first that the list portion also needed to be
modified, perhaps because I am not coding that myself... So now that
it is becoming more complicated what about just using AutovacMemCxt?
This would simplify the list of VacuumRelation entries and the
RangeVar creation as well, and honestly this is ugly and there are no
other similar patterns in the backend code:
+1
This would become way more readable by using makeRangeVar() and the
new makeVacuumRelation. As this is partly my fault that we are at this
state, I am fine as well to remove this burden from you, Nathan, and
fix that myself in a new version. And I don't want to step on your
toes either :)
No worries, I can take care of it. I appreciate your patience with all
of these reviews.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/4/17, 7:14 AM, "Bossart, Nathan" <bossartn@amazon.com> wrote:
On 9/3/17, 11:46 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
I did not consider first that the list portion also needed to be
modified, perhaps because I am not coding that myself... So now that
it is becoming more complicated what about just using AutovacMemCxt?
This would simplify the list of VacuumRelation entries and the
RangeVar creation as well, and honestly this is ugly and there are no
other similar patterns in the backend code:+1
I've made this change in v14 of the main patch.
In case others had opinions regarding the de-duplication patch, I've
attached that again as well.
Nathan
Attachments:
dedupe_vacuum_relations_v3.patchapplication/octet-stream; name=dedupe_vacuum_relations_v3.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a9fe412351..fbc442f1a3 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -71,6 +71,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
static void check_columns_exist(List *relations);
+static void dedupe_relations(List **relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -269,6 +270,17 @@ vacuum(int options, List *relations, VacuumParams *params,
check_columns_exist(relations);
/*
+ * Now dedupe the list to avoid any redundant work (e.g. user specifies
+ * the same relation twice). We also take care of combining any
+ * separate column lists for duplicate relations.
+ *
+ * We do this after resolving the OIDs so that we do not miss entries
+ * that have unequal RangeVars but resolve to the same OIDs. For
+ * example, "foo" and "public.foo" could be the same relation.
+ */
+ dedupe_relations(&relations);
+
+ /*
* Decide whether we need to start/commit our own transactions.
*
* For VACUUM (with or without ANALYZE): always do so, so that we can
@@ -609,6 +621,81 @@ check_columns_exist(List *relations)
}
/*
+ * De-duplicate all relations and columns specified. The OIDs for each relation
+ * must already be determined before calling this function.
+ */
+static void
+dedupe_relations(List **relations)
+{
+ int i;
+ int next_elem_index = 0;
+ List *duplicates = NIL; /* indexes of entries to remove */
+ ListCell *lc;
+ List *temp_relations = NIL;
+
+ if (*relations == NIL) /* nothing to do */
+ return;
+
+ /* mark any duplicates and combine the column lists into the first match */
+ foreach(lc, *relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+
+ ++next_elem_index;
+
+ /*
+ * If this one has already been marked as a duplicate, it has
+ * already been processed, and there's nothing left to do.
+ */
+ if (list_member_int(duplicates, next_elem_index - 1))
+ continue;
+
+ /* remove any duplicates in the column list */
+ relation->va_cols = list_concat_unique(NIL, relation->va_cols);
+
+ /* now look for any duplicates in the remainder of the list */
+ for (i = next_elem_index; i < list_length(*relations); ++i)
+ {
+ VacuumRelation *nth_rel = list_nth_node(VacuumRelation, *relations, i);
+
+ /* if already processed or not equal, skip */
+ if (list_member_int(duplicates, i) || relation->oid != nth_rel->oid)
+ continue;
+
+ /*
+ * For ANALYZE, if a specified relation has an empty
+ * column list, it means that all columns should be
+ * analyzed. Therefore, any time a relation is
+ * specified with an empty column list, it overrides any
+ * other column lists specified for that relation.
+ */
+ if (relation->va_cols == NIL || nth_rel->va_cols == NIL)
+ relation->va_cols = NIL;
+ else /* combine the column lists */
+ relation->va_cols = list_concat_unique(relation->va_cols, nth_rel->va_cols);
+
+ duplicates = lappend_int(duplicates, i);
+ }
+ }
+
+ /* nothing else needed if we did not find any duplicates */
+ if (duplicates == NIL)
+ return;
+
+ for (i = 0; i < list_length(*relations); ++i)
+ {
+ /* if we are a duplicate entry, skip */
+ if (list_member_int(duplicates, i))
+ continue;
+
+ /* add non-duplicate entry to the final list */
+ temp_relations = lappend(temp_relations, list_nth_node(VacuumRelation, *relations, i));
+ }
+
+ *relations = temp_relations;
+}
+
+/*
* vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
*
* The output parameters are:
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d29dd8f3a3..113f59cfa9 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -120,6 +120,7 @@ ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 991bea7752..1134a58be5 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -93,6 +93,7 @@ ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
vacuum_multiple_tables_v14.patchapplication/octet-stream; name=vacuum_multiple_tables_v14.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee101df..c69a3490bf 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d117..3a0afa3b74 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fbad13ea94..7996674630 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa181207a..a9fe412351 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +69,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +195,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +248,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +334,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +355,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +410,91 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,6 +512,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +527,18 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo = makeVacuumRelation(NULL, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +546,66 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1441,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f9ddf4ed76..a655a33876 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3766,8 +3766,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5214,6 +5225,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03633..508b7c95a3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039da9..60d6addf0f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99baf..d97b507232 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10128,11 +10130,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10142,8 +10143,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10162,18 +10162,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10207,18 +10203,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10246,6 +10240,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0a9d..b171049e9b 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a9035112e9..7a7b793ddf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1817..12d78ff923 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3363..ffeeb4919b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a75da..6ecafd11ee 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663087..d29dd8f3a3 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,38 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04917..991bea7752 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Mon, Sep 4, 2017 at 11:47 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
I've made this change in v14 of the main patch.
In case others had opinions regarding the de-duplication patch, I've
attached that again as well.
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
That's way better, thanks for the new patch.
So vacuum_multiple_tables_v14.patch is good for a committer in my
opinion. With this patch, if the same relation is specified multiple
times, then it gets vacuum'ed that many times. Using the same column
multi-times results in an error as on HEAD, but that's not a new
problem with this patch.
So I would tend to think that the same column specified multiple times
should cause an error, and that we could let VACUUM run work N times
on a relation if it is specified this much. This feels more natural,
at least to me, and it keeps the code simple.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Sep 5, 2017 at 10:16 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Mon, Sep 4, 2017 at 11:47 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
I've made this change in v14 of the main patch.
In case others had opinions regarding the de-duplication patch, I've
attached that again as well.+ /* + * Create the relation list in a long-lived memory context so that it + * survives transaction boundaries. + */ + old_cxt = MemoryContextSwitchTo(AutovacMemCxt); + rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1); + rel = makeVacuumRelation(rangevar, NIL, tab->at_relid); + rel_list = list_make1(rel); + MemoryContextSwitchTo(old_cxt); That's way better, thanks for the new patch.So vacuum_multiple_tables_v14.patch is good for a committer in my
opinion.
In get_rel_oids() we often switch the memory context to vac_context
and switch back. As a result almost code in get_rel_oids() is working
in vac_context. Maybe we can switch memory context before and after
the calling get_rel_oids?
Regards,
--
Masahiko Sawada
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Sep 5, 2017 at 12:05 PM, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
In get_rel_oids() we often switch the memory context to vac_context
and switch back. As a result almost code in get_rel_oids() is working
in vac_context. Maybe we can switch memory context before and after
the calling get_rel_oids?
I thought about that as well, and it seemed to me that the current
patch approach is less bug-prone for the future if get_rel_oids() gets
called in some future code paths.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5 September 2017 at 02:16, Michael Paquier <michael.paquier@gmail.com> wrote:
On Mon, Sep 4, 2017 at 11:47 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
I've made this change in v14 of the main patch.
In case others had opinions regarding the de-duplication patch, I've
attached that again as well.+ /* + * Create the relation list in a long-lived memory context so that it + * survives transaction boundaries. + */ + old_cxt = MemoryContextSwitchTo(AutovacMemCxt); + rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1); + rel = makeVacuumRelation(rangevar, NIL, tab->at_relid); + rel_list = list_make1(rel); + MemoryContextSwitchTo(old_cxt); That's way better, thanks for the new patch.So vacuum_multiple_tables_v14.patch is good for a committer in my
opinion. With this patch, if the same relation is specified multiple
times, then it gets vacuum'ed that many times. Using the same column
multi-times results in an error as on HEAD, but that's not a new
problem with this patch.So I would tend to think that the same column specified multiple times
should cause an error, and that we could let VACUUM run work N times
on a relation if it is specified this much. This feels more natural,
at least to me, and it keeps the code simple.
ISTM there is no difference between
VACUUM a, b
and
VACUUM a; VACUUM b;
If we want to keep the code simple we must surely consider whether the
patch has any utility.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Sep 5, 2017 at 12:24 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Tue, Sep 5, 2017 at 12:05 PM, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
In get_rel_oids() we often switch the memory context to vac_context
and switch back. As a result almost code in get_rel_oids() is working
in vac_context. Maybe we can switch memory context before and after
the calling get_rel_oids?I thought about that as well, and it seemed to me that the current
patch approach is less bug-prone for the future if get_rel_oids() gets
called in some future code paths.
Okay, I agree. Also I found that dedupe_relations() eventually
allocates the list in current memory context that may not be
vac_context and set it to *relations at the end of that function. I
think we should switch the memory context to vac_context before doing
that. Or to more simplify the code maybe we can do the all treatment
of the relations list after switching to vac_context. For example,
oldcontext = MemoryContextSwtichTo(vac_context)
relations = copyObject(relations);
get_rel_oids(&relations);
check_colums_exist(relations);
dedupe_relations(&relations);
MemoryContextSwtichTo(oldcontext);
Regards,
--
Masahiko Sawada
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/4/17, 10:32 PM, "Simon Riggs" <simon@2ndquadrant.com> wrote:
ISTM there is no difference between
VACUUM a, b
and
VACUUM a; VACUUM b;If we want to keep the code simple we must surely consider whether the
patch has any utility.
Yes, this is true, but I think the convenience factor is a bit
understated with that example. For example, if you need to manually
cleanup several tables for XID purposes,
VACUUM FREEZE VERBOSE table1;
VACUUM FREEZE VERBOSE table2;
VACUUM FREEZE VERBOSE table3;
VACUUM FREEZE VERBOSE table4;
VACUUM FREEZE VERBOSE table5;
becomes
VACUUM FREEZE VERBOSE table1, table2, table3, table4, table5;
I would consider even this to be a relatively modest example compared
to the sorts of things users might do.
In addition, I'd argue that this feels like a natural extension of the
VACUUM command, one that I, like others much earlier in this thread,
was surprised to learn wasn't supported.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"Bossart, Nathan" <bossartn@amazon.com> writes:
On 9/4/17, 10:32 PM, "Simon Riggs" <simon@2ndquadrant.com> wrote:
If we want to keep the code simple we must surely consider whether the
patch has any utility.
... I'd argue that this feels like a natural extension of the
VACUUM command, one that I, like others much earlier in this thread,
was surprised to learn wasn't supported.
Yeah. To me, one big argument for allowing multiple target tables is that
we allow it for other common utility commands such as TRUNCATE or LOCK
TABLE.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/4/17, 8:16 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
So vacuum_multiple_tables_v14.patch is good for a committer in my
opinion. With this patch, if the same relation is specified multiple
times, then it gets vacuum'ed that many times. Using the same column
multi-times results in an error as on HEAD, but that's not a new
problem with this patch.
Thanks!
So I would tend to think that the same column specified multiple times
should cause an error, and that we could let VACUUM run work N times
on a relation if it is specified this much. This feels more natural,
at least to me, and it keeps the code simple.
I think that is a reasonable approach. Another option I was thinking
about was to de-duplicate only the individual column lists. This
alternative approach might be a bit more user-friendly, but I am
beginning to agree with you that perhaps we should not try to infer
the intent of the user in these "duplicate" scenarios.
I'll work on converting the existing de-duplication patch into
something more like what you suggested.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane wrote:
"Bossart, Nathan" <bossartn@amazon.com> writes:
On 9/4/17, 10:32 PM, "Simon Riggs" <simon@2ndquadrant.com> wrote:
If we want to keep the code simple we must surely consider whether the
patch has any utility.... I'd argue that this feels like a natural extension of the
VACUUM command, one that I, like others much earlier in this thread,
was surprised to learn wasn't supported.Yeah. To me, one big argument for allowing multiple target tables is that
we allow it for other common utility commands such as TRUNCATE or LOCK
TABLE.
TRUNCATE has actual an feature behind its multi-table ability: you can
truncate tables linked by FKs that way, and not otherwise. VACUUM, like
LOCK TABLE, have no such benefit.
(If one is programatically locking multiple tables, it is easier to do
one table per command than many in one command, anyway.)
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Sep 6, 2017 at 2:36 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 9/4/17, 8:16 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
So I would tend to think that the same column specified multiple times
should cause an error, and that we could let VACUUM run work N times
on a relation if it is specified this much. This feels more natural,
at least to me, and it keeps the code simple.I think that is a reasonable approach. Another option I was thinking
about was to de-duplicate only the individual column lists. This
alternative approach might be a bit more user-friendly, but I am
beginning to agree with you that perhaps we should not try to infer
the intent of the user in these "duplicate" scenarios.I'll work on converting the existing de-duplication patch into
something more like what you suggested.
Cool. I'll look at anything you have.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/5/17, 5:53 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
On Wed, Sep 6, 2017 at 2:36 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 9/4/17, 8:16 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
So I would tend to think that the same column specified multiple times
should cause an error, and that we could let VACUUM run work N times
on a relation if it is specified this much. This feels more natural,
at least to me, and it keeps the code simple.I think that is a reasonable approach. Another option I was thinking
about was to de-duplicate only the individual column lists. This
alternative approach might be a bit more user-friendly, but I am
beginning to agree with you that perhaps we should not try to infer
the intent of the user in these "duplicate" scenarios.I'll work on converting the existing de-duplication patch into
something more like what you suggested.Cool. I'll look at anything you have.
I've attached v1 of this patch. I think we might want to refactor the
code for retrieving the relation name from a RangeVar, but it would
probably be better to do that in a separate patch.
Nathan
Attachments:
vacuum_multiple_tables_v14.patchapplication/octet-stream; name=vacuum_multiple_tables_v14.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee101df..c69a3490bf 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d117..3a0afa3b74 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fbad13ea94..7996674630 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa181207a..a9fe412351 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +69,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +195,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +248,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +334,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +355,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +410,91 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,6 +512,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +527,18 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo = makeVacuumRelation(NULL, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +546,66 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ return;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1441,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f9ddf4ed76..a655a33876 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3766,8 +3766,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5214,6 +5225,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03633..508b7c95a3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3360,6 +3369,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039da9..60d6addf0f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99baf..d97b507232 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10128,11 +10130,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10142,8 +10143,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10162,18 +10162,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10207,18 +10203,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10246,6 +10240,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0a9d..b171049e9b 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a9035112e9..7a7b793ddf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1817..12d78ff923 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3363..ffeeb4919b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a75da..6ecafd11ee 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3090,12 +3090,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663087..d29dd8f3a3 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,38 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04917..991bea7752 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
error_on_duplicate_columns_in_analyze_v1.patchapplication/octet-stream; name=error_on_duplicate_columns_in_analyze_v1.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a9fe412..c2b7e35 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -70,7 +70,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
-static void check_columns_exist(List *relations);
+static void check_column_lists(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -265,8 +265,11 @@ vacuum(int options, List *relations, VacuumParams *params,
* Check that all specified columns exist so that we can fast-fail
* commands with multiple tables. If the column disappears before we
* actually process it, we will emit a WARNING and skip it later on.
+ *
+ * Also verify that there are no duplicate columns specified in any of
+ * the column lists.
*/
- check_columns_exist(relations);
+ check_column_lists(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -550,16 +553,20 @@ get_rel_oids(List **vacrels)
}
/*
- * Check that all specified columns for the relations exist.
+ * Check that all specified columns for the relations exist and that there are
+ * no duplicate columns specified in each list.
*
* This function emits an ERROR if the relation or the columns specified for
* a relation cannot be found. This is used to pre-validate any column lists
* specified in VACUUM/ANALYZE commands. If any columns disappear between
* calling this function and when we actually get to processing them, we will
* emit a WARNING and skip it at that time.
+ *
+ * This function also emits an ERROR if any column list contains duplicate
+ * entries, as ANALYZE will fail in that case.
*/
static void
-check_columns_exist(List *relations)
+check_column_lists(List *relations)
{
ListCell *relation_lc;
foreach(relation_lc, relations)
@@ -567,6 +574,7 @@ check_columns_exist(List *relations)
VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
Relation rel;
ListCell *column_lc;
+ List *unique_columns;
if (relation->va_cols == NIL) /* nothing to check */
return;
@@ -593,6 +601,24 @@ check_columns_exist(List *relations)
relation->relation->relname)));
}
+ /* verify that there are no duplicate columns specified */
+ unique_columns = list_concat_unique(NIL, relation->va_cols);
+ if (list_length(unique_columns) != list_length(relation->va_cols))
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s.%s\" contains duplicates",
+ relation->relation->schemaname, relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s\" contains duplicates",
+ relation->relation->relname)));
+ }
+
foreach(column_lc, relation->va_cols)
{
char *col = strVal(lfirst(column_lc));
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d29dd8f..cb377ab 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -97,6 +97,8 @@ VACUUM (FREEZE) vaccluster, does_not_exist;
ERROR: relation "does_not_exist" does not exist
VACUUM (FREEZE, ANALYZE) vactst (i);
VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "vactst" contains duplicates
VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
@@ -120,6 +122,9 @@ ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "vactst" contains duplicates
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 991bea7..1134a58 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -93,6 +93,7 @@ ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
DROP TABLE vaccluster;
DROP TABLE vactst;
On Thu, Sep 7, 2017 at 9:46 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
I've attached v1 of this patch. I think we might want to refactor the
code for retrieving the relation name from a RangeVar, but it would
probably be better to do that in a separate patch.
Using the patch checking for duplicate columns:
=# create table aa (a int);
CREATE TABLE
=# vacuum ANALYZE aa(z, z);
ERROR: 0A000: column lists cannot have duplicate entries
HINT: the column list specified for relation "aa" contains duplicates
LOCATION: check_column_lists, vacuum.c:619
Shouldn't the priority be given to undefined columns instead of
duplicates? You may want to add a test for that as well.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/7/17, 2:33 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Using the patch checking for duplicate columns:
=# create table aa (a int);
CREATE TABLE
=# vacuum ANALYZE aa(z, z);
ERROR: 0A000: column lists cannot have duplicate entries
HINT: the column list specified for relation "aa" contains duplicates
LOCATION: check_column_lists, vacuum.c:619
Shouldn't the priority be given to undefined columns instead of
duplicates? You may want to add a test for that as well.
I agree. I've fixed this and added a couple relevant tests cases in
v2.
I've also attached a v15 of the main patch. In check_columns_exist(),
there was a 'return' that should be a 'continue'. This caused us to
skip the column existence checks for column lists defined after a table
with no column list.
Nathan
Attachments:
error_on_duplicate_columns_in_analyze_v2.patchapplication/octet-stream; name=error_on_duplicate_columns_in_analyze_v2.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 756be3d..5d99630 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -70,7 +70,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
-static void check_columns_exist(List *relations);
+static void check_column_lists(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -265,8 +265,11 @@ vacuum(int options, List *relations, VacuumParams *params,
* Check that all specified columns exist so that we can fast-fail
* commands with multiple tables. If the column disappears before we
* actually process it, we will emit a WARNING and skip it later on.
+ *
+ * Also verify that there are no duplicate columns specified in any of
+ * the column lists.
*/
- check_columns_exist(relations);
+ check_column_lists(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -550,17 +553,24 @@ get_rel_oids(List **vacrels)
}
/*
- * Check that all specified columns for the relations exist.
+ * Check that all specified columns for the relations exist and that there are
+ * no duplicate columns specified in each list.
*
* This function emits an ERROR if the relation or the columns specified for
* a relation cannot be found. This is used to pre-validate any column lists
* specified in VACUUM/ANALYZE commands. If any columns disappear between
* calling this function and when we actually get to processing them, we will
* emit a WARNING and skip it at that time.
+ *
+ * This function also emits an ERROR if any column list contains duplicate
+ * entries, as ANALYZE will fail in that case.
*/
static void
-check_columns_exist(List *relations)
+check_column_lists(List *relations)
{
+ /*
+ * First, verify that all specified columns exist.
+ */
ListCell *relation_lc;
foreach(relation_lc, relations)
{
@@ -606,6 +616,40 @@ check_columns_exist(List *relations)
}
relation_close(rel, NoLock);
}
+
+ /*
+ * Verify that there are no duplicate entries in any of the column
+ * lists.
+ *
+ * We do this after checking that all columns exist so that nonexistent
+ * columns ERROR with higher priority than duplicates.
+ */
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ List *unique_columns;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ continue;
+
+ unique_columns = list_concat_unique(NIL, relation->va_cols);
+ if (list_length(unique_columns) == list_length(relation->va_cols))
+ continue;
+
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s.%s\" contains duplicates",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s\" contains duplicates",
+ relation->relation->relname)));
+ }
}
/*
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d29dd8f..5e8f3d9 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -97,6 +97,8 @@ VACUUM (FREEZE) vaccluster, does_not_exist;
ERROR: relation "does_not_exist" does not exist
VACUUM (FREEZE, ANALYZE) vactst (i);
VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "vactst" contains duplicates
VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
@@ -113,6 +115,8 @@ HINT: A column list was specified for relation "vaccluster".
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
ERROR: ANALYZE option must be specified when a column list is provided
HINT: A column list was specified for relation "vactst".
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
VACUUM;
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
@@ -120,6 +124,11 @@ ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "vactst" contains duplicates
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
+ERROR: column "does_not_exist" of relation "vactst" does not exist
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 991bea7..6671bda 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -87,12 +87,15 @@ VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
VACUUM vactst (i);
VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
VACUUM;
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
DROP TABLE vaccluster;
DROP TABLE vactst;
vacuum_multiple_tables_v15.patchapplication/octet-stream; name=vacuum_multiple_tables_v15.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e..061e8a8 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..756be3d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +69,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +195,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +248,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +334,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +355,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +410,91 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,6 +512,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +527,18 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo = makeVacuumRelation(NULL, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +546,66 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ continue;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1441,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9bae264..b5ff58f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -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;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 11731da..59fbb23 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -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;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5eb3981..b720faa 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10144,11 +10146,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10158,8 +10159,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10178,18 +10178,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10223,18 +10219,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10262,6 +10256,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..b171049 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3171815..cd9dde9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3097,12 +3097,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..d29dd8f 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,38 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..991bea7 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Fri, Sep 8, 2017 at 7:27 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 9/7/17, 2:33 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Using the patch checking for duplicate columns:
=# create table aa (a int);
CREATE TABLE
=# vacuum ANALYZE aa(z, z);
ERROR: 0A000: column lists cannot have duplicate entries
HINT: the column list specified for relation "aa" contains duplicates
LOCATION: check_column_lists, vacuum.c:619
Shouldn't the priority be given to undefined columns instead of
duplicates? You may want to add a test for that as well.I agree. I've fixed this and added a couple relevant tests cases in
v2.
Thanks. This looks now correct to me. Except that:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation
\"%s\" contains duplicates",
+ relation->relation->relname)));
This should use ERRCODE_DUPLICATE_COLUMN.
I've also attached a v15 of the main patch. In check_columns_exist(),
there was a 'return' that should be a 'continue'. This caused us to
skip the column existence checks for column lists defined after a table
with no column list.
I can see that. Nicely spotted.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/8/17, 1:27 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Thanks. This looks now correct to me. Except that: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("column lists cannot have duplicate entries"), + errhint("the column list specified for relation \"%s\" contains duplicates", + relation->relation->relname))); This should use ERRCODE_DUPLICATE_COLUMN.
Absolutely. This is fixed in v3.
Nathan
Attachments:
error_on_duplicate_columns_in_analyze_v3.patchapplication/octet-stream; name=error_on_duplicate_columns_in_analyze_v3.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 756be3d..7ba7da7 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -70,7 +70,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
-static void check_columns_exist(List *relations);
+static void check_column_lists(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -265,8 +265,11 @@ vacuum(int options, List *relations, VacuumParams *params,
* Check that all specified columns exist so that we can fast-fail
* commands with multiple tables. If the column disappears before we
* actually process it, we will emit a WARNING and skip it later on.
+ *
+ * Also verify that there are no duplicate columns specified in any of
+ * the column lists.
*/
- check_columns_exist(relations);
+ check_column_lists(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -550,17 +553,24 @@ get_rel_oids(List **vacrels)
}
/*
- * Check that all specified columns for the relations exist.
+ * Check that all specified columns for the relations exist and that there are
+ * no duplicate columns specified in each list.
*
* This function emits an ERROR if the relation or the columns specified for
* a relation cannot be found. This is used to pre-validate any column lists
* specified in VACUUM/ANALYZE commands. If any columns disappear between
* calling this function and when we actually get to processing them, we will
* emit a WARNING and skip it at that time.
+ *
+ * This function also emits an ERROR if any column list contains duplicate
+ * entries, as ANALYZE will fail in that case.
*/
static void
-check_columns_exist(List *relations)
+check_column_lists(List *relations)
{
+ /*
+ * First, verify that all specified columns exist.
+ */
ListCell *relation_lc;
foreach(relation_lc, relations)
{
@@ -606,6 +616,40 @@ check_columns_exist(List *relations)
}
relation_close(rel, NoLock);
}
+
+ /*
+ * Verify that there are no duplicate entries in any of the column
+ * lists.
+ *
+ * We do this after checking that all columns exist so that nonexistent
+ * columns ERROR with higher priority than duplicates.
+ */
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ List *unique_columns;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ continue;
+
+ unique_columns = list_concat_unique(NIL, relation->va_cols);
+ if (list_length(unique_columns) == list_length(relation->va_cols))
+ continue;
+
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s.%s\" contains duplicates",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s\" contains duplicates",
+ relation->relation->relname)));
+ }
}
/*
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d29dd8f..5e8f3d9 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -97,6 +97,8 @@ VACUUM (FREEZE) vaccluster, does_not_exist;
ERROR: relation "does_not_exist" does not exist
VACUUM (FREEZE, ANALYZE) vactst (i);
VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "vactst" contains duplicates
VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
@@ -113,6 +115,8 @@ HINT: A column list was specified for relation "vaccluster".
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
ERROR: ANALYZE option must be specified when a column list is provided
HINT: A column list was specified for relation "vactst".
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
VACUUM;
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
@@ -120,6 +124,11 @@ ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "vactst" contains duplicates
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
+ERROR: column "does_not_exist" of relation "vactst" does not exist
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 991bea7..6671bda 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -87,12 +87,15 @@ VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
VACUUM vactst (i);
VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
VACUUM;
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
DROP TABLE vaccluster;
DROP TABLE vactst;
vacuum_multiple_tables_v15.patchapplication/octet-stream; name=vacuum_multiple_tables_v15.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e..061e8a8 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -152,7 +152,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..756be3d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -67,7 +69,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +93,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +121,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +129,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +144,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +195,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +248,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +334,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +355,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +410,91 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,6 +512,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
@@ -465,7 +527,18 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo = makeVacuumRelation(NULL, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +546,66 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ continue;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1309,6 +1441,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9bae264..b5ff58f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -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;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 11731da..59fbb23 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -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;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5eb3981..b720faa 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10144,11 +10146,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10158,8 +10159,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10178,18 +10178,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10223,18 +10219,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10262,6 +10256,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..b171049 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3171815..cd9dde9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3097,12 +3097,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..d29dd8f 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,38 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..991bea7 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,30 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM;
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Sat, Sep 9, 2017 at 2:05 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 9/8/17, 1:27 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Thanks. This looks now correct to me. Except that: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("column lists cannot have duplicate entries"), + errhint("the column list specified for relation \"%s\" contains duplicates", + relation->relation->relname))); This should use ERRCODE_DUPLICATE_COLUMN.Absolutely. This is fixed in v3.
In the duplicate patch, it seems to me that you can save one lookup at
the list of VacuumRelation items by checking for column duplicates
after checking that all the columns are defined. If you put the
duplicate check before closing the relation you can also use the
schema name associated with the Relation.
+ if (i == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
This could use the schema name unconditionally as you hold a Relation
here, using RelationGetNamespace().
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess() && relation)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
Hm. So if the relation with the defined OID is not found, then we'd
use the RangeVar, but this one may not be set here:
+ /*
+ * It is safe to leave everything except the OID empty here.
+ * Since no tables were specified in the VacuumStmt, we know
+ * we don't have any columns to keep track of. Also, we do
+ * not need the RangeVar, because it is only used for error
+ * messaging when specific relations are chosen.
+ */
+ rel_oid = HeapTupleGetOid(tuple);
+ relinfo = makeVacuumRelation(NULL, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
So if the relation is analyzed but skipped, we would have no idea that
it actually got skipped because there are no reports about it. That's
not really user-friendly. I am wondering if we should not instead have
analyze_rel also enforce the presence of a RangeVar, and adding an
assert at the beginning of the function to undertline that, and also
do the same for vacuum(). It would make things also consistent with
vacuum() which now implies on HEAD that a RangeVar *must* be
specified.
Sorry for noticing that just now, I am switching the patch back to
waiting on author.
Are there opinions about back-patching the patch checking for
duplicate columns? Stable branches now complain about an unhelpful
error message.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/9/17, 7:28 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
In the duplicate patch, it seems to me that you can save one lookup at
the list of VacuumRelation items by checking for column duplicates
after checking that all the columns are defined. If you put the
duplicate check before closing the relation you can also use the
schema name associated with the Relation.
I did this so that the ERROR prioritization would be enforced across all
relations. For example:
VACUUM ANALYZE table1 (a, a), table2 (does_not_exist);
If I combine the 'for' loops to save a lookup, this example behaves
differently. Instead of an ERROR for the nonexistent column, you would
hit an ERROR for the duplicate column in table1's list. However, I do
not mind changing this.
+ if (i == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + col, RelationGetRelationName(rel)))); This could use the schema name unconditionally as you hold a Relation here, using RelationGetNamespace().
Sure, I think this is a good idea. I'll make this change in the next
version of the patch.
if (!onerel) + { + /* + * If one of the relations specified by the user has disappeared + * since we last looked it up, let them know so that they do not + * think it was actually analyzed. + */ + if (!IsAutoVacuumWorkerProcess() && relation) + ereport(WARNING, + (errmsg("skipping \"%s\" --- relation no longer exists", + relation->relname))); + return; + } Hm. So if the relation with the defined OID is not found, then we'd use the RangeVar, but this one may not be set here: + /* + * It is safe to leave everything except the OID empty here. + * Since no tables were specified in the VacuumStmt, we know + * we don't have any columns to keep track of. Also, we do + * not need the RangeVar, because it is only used for error + * messaging when specific relations are chosen. + */ + rel_oid = HeapTupleGetOid(tuple); + relinfo = makeVacuumRelation(NULL, NIL, rel_oid); + vacrels_tmp = lappend(vacrels_tmp, relinfo); So if the relation is analyzed but skipped, we would have no idea that it actually got skipped because there are no reports about it. That's not really user-friendly. I am wondering if we should not instead have analyze_rel also enforce the presence of a RangeVar, and adding an assert at the beginning of the function to undertline that, and also do the same for vacuum(). It would make things also consistent with vacuum() which now implies on HEAD that a RangeVar *must* be specified.
I agree that it is nice to see when relations are skipped, but I do not
know if the WARNING messages would provide much value for this
particular use case (i.e. 'VACUUM;'). If a user does not provide a list
of tables to VACUUM, they might not care too much about WARNINGs for
dropped tables.
Are there opinions about back-patching the patch checking for
duplicate columns? Stable branches now complain about an unhelpful
error message.
I wouldn't mind drafting something up for the stable branches.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Sep 11, 2017 at 9:38 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
I agree that it is nice to see when relations are skipped, but I do not
know if the WARNING messages would provide much value for this
particular use case (i.e. 'VACUUM;'). If a user does not provide a list
of tables to VACUUM, they might not care too much about WARNINGs for
dropped tables.
Some users trigger manual VACUUM with cron jobs in moments of
low-activity as autovacuum may sometimes not be able to keep up with
hte bloat cleanup. It seems to me that getting WARNING messages is
particularly important for partitioned tables.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/9/17, 7:28 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
In the duplicate patch, it seems to me that you can save one lookup at
the list of VacuumRelation items by checking for column duplicates
after checking that all the columns are defined. If you put the
duplicate check before closing the relation you can also use the
schema name associated with the Relation.
I've made these changes in v4 of the duplicate patch.
+ if (i == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + col, RelationGetRelationName(rel)))); This could use the schema name unconditionally as you hold a Relation here, using RelationGetNamespace().
This is added in v16 of the main patch.
So if the relation is analyzed but skipped, we would have no idea that
it actually got skipped because there are no reports about it. That's
not really user-friendly. I am wondering if we should not instead have
analyze_rel also enforce the presence of a RangeVar, and adding an
assert at the beginning of the function to undertline that, and also
do the same for vacuum(). It would make things also consistent with
vacuum() which now implies on HEAD that a RangeVar *must* be
specified.
I've made these changes in v16 of the main patch.
Nathan
Attachments:
vacuum_multiple_tables_v16.patchapplication/octet-stream; name=vacuum_multiple_tables_v16.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee101df..c69a3490bf 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d117..3a0afa3b74 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e96b..e9cfd903d2 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
@@ -152,7 +154,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +397,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa181207a..d46e686e6c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +48,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +70,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +94,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +122,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +130,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +145,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +196,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +249,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +335,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +356,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +411,91 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +513,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +531,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +546,77 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+ char *schemaname;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ continue;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ schemaname = get_namespace_name(RelationGetNamespace(rel));
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i != InvalidAttrNumber)
+ continue;
+
+ if (schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s.%s\" does not exist",
+ col, schemaname, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1232,6 +1375,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1309,6 +1453,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14e2b..d32c4e688b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b9146a..1d0d779609 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039da9..60d6addf0f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818c9b..b3fe50efb6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0a9d..b171049e9b 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a9035112e9..7a7b793ddf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1817..12d78ff923 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3363..ffeeb4919b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69753..2adbd54dd8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0d400d9778..78fd285b66 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1282,13 +1282,13 @@ ERROR: "myview" is not a table, composite type, or foreign table
drop view myview;
-- test some commands to make sure they fail on the dropped column
analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
vacuum analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
vacuum analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
comment on column atacc1.a is 'testing';
ERROR: column "a" of relation "atacc1" does not exist
comment on column atacc1."........pg.dropped.1........" is 'testing';
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663087..62c1a03685 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,37 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04917..10e0de0113 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,29 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
error_on_duplicate_columns_in_analyze_v4.patchapplication/octet-stream; name=error_on_duplicate_columns_in_analyze_v4.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d46e686e6c..c1ff4d7e7f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -71,7 +71,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
-static void check_columns_exist(List *relations);
+static void check_column_lists(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -266,8 +266,11 @@ vacuum(int options, List *relations, VacuumParams *params,
* Check that all specified columns exist so that we can fast-fail
* commands with multiple tables. If the column disappears before we
* actually process it, we will emit a WARNING and skip it later on.
+ *
+ * Also verify that there are no duplicate columns specified in any of
+ * the column lists.
*/
- check_columns_exist(relations);
+ check_column_lists(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -550,16 +553,20 @@ get_rel_oids(List **vacrels)
}
/*
- * Check that all specified columns for the relations exist.
+ * Check that all specified columns for the relations exist and that there are
+ * no duplicate columns specified in each list.
*
* This function emits an ERROR if the relation or the columns specified for
* a relation cannot be found. This is used to pre-validate any column lists
* specified in VACUUM/ANALYZE commands. If any columns disappear between
* calling this function and when we actually get to processing them, we will
* emit a WARNING and skip it at that time.
+ *
+ * This function also emits an ERROR if any column list contains duplicate
+ * entries, as ANALYZE will fail in that case.
*/
static void
-check_columns_exist(List *relations)
+check_column_lists(List *relations)
{
ListCell *relation_lc;
foreach(relation_lc, relations)
@@ -568,6 +575,7 @@ check_columns_exist(List *relations)
Relation rel;
ListCell *column_lc;
char *schemaname;
+ List *unique_columns;
if (relation->va_cols == NIL) /* nothing to check */
continue;
@@ -596,6 +604,9 @@ check_columns_exist(List *relations)
schemaname = get_namespace_name(RelationGetNamespace(rel));
+ /*
+ * First, verify that all specified columns exist.
+ */
foreach(column_lc, relation->va_cols)
{
char *col = strVal(lfirst(column_lc));
@@ -615,6 +626,29 @@ check_columns_exist(List *relations)
errmsg("column \%s\" of relation \"%s\" does not exist",
col, RelationGetRelationName(rel))));
}
+
+ /*
+ * Verify that there are no duplicate entries in the column list.
+ *
+ * We do this after checking that all columns exist so that nonexistent
+ * columns ERROR with higher priority than duplicates.
+ */
+ unique_columns = list_concat_unique(NIL, relation->va_cols);
+ if (list_length(unique_columns) != list_length(relation->va_cols))
+ {
+ if (schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s.%s\" contains duplicates",
+ schemaname, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s\" contains duplicates",
+ RelationGetRelationName(rel))));
+ }
relation_close(rel, NoLock);
}
}
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 62c1a03685..4272d1136c 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -97,6 +97,8 @@ VACUUM (FREEZE) vaccluster, does_not_exist;
ERROR: relation "does_not_exist" does not exist
VACUUM (FREEZE, ANALYZE) vactst (i);
VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.vactst" contains duplicates
VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
@@ -113,12 +115,19 @@ HINT: A column list was specified for relation "vaccluster".
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
ERROR: ANALYZE option must be specified when a column list is provided
HINT: A column list was specified for relation "vactst".
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.vactst" contains duplicates
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vactst" does not exist
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 10e0de0113..6ccba3b8c5 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -87,11 +87,14 @@ VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
VACUUM vactst (i);
VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
DROP TABLE vaccluster;
DROP TABLE vactst;
On Mon, Sep 11, 2017 at 2:05 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
+ if (i == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + col, RelationGetRelationName(rel)))); This could use the schema name unconditionally as you hold a Relation here, using RelationGetNamespace().This is added in v16 of the main patch.
So if the relation is analyzed but skipped, we would have no idea that
it actually got skipped because there are no reports about it. That's
not really user-friendly. I am wondering if we should not instead have
analyze_rel also enforce the presence of a RangeVar, and adding an
assert at the beginning of the function to undertline that, and also
do the same for vacuum(). It would make things also consistent with
vacuum() which now implies on HEAD that a RangeVar *must* be
specified.I've made these changes in v16 of the main patch.
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ VacuumRelation *tmp = copyObject(relinfo);
+ Oid part_oid = lfirst_oid(part_lc);
+ tmp->oid = part_oid;
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
I thought that you would have changed that as well, but that's not
completely complete... In my opinion, HEAD is wrong in using the same
RangeVar for error reporting for a parent table and its partitions, so
that's not completely the fault of your patch. But I think that as
this patch makes vacuum routines smarter, you should create a new
RangeVar using makeRangeVar as you hold the OID of the child partition
in this code path. This would allow error reports to actually use the
data of the partition saved here instead of the parent data.
The rest looks fine to me.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/11/17, 9:28 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+ if (include_parts) + { + List *partition_oids = find_all_inheritors(relid, NoLock, NULL); + ListCell *part_lc; + foreach(part_lc, partition_oids) + { + VacuumRelation *tmp = copyObject(relinfo); + Oid part_oid = lfirst_oid(part_lc); + tmp->oid = part_oid; + vacrels_tmp = lappend(vacrels_tmp, tmp); + } + } I thought that you would have changed that as well, but that's not completely complete... In my opinion, HEAD is wrong in using the same RangeVar for error reporting for a parent table and its partitions, so that's not completely the fault of your patch. But I think that as this patch makes vacuum routines smarter, you should create a new RangeVar using makeRangeVar as you hold the OID of the child partition in this code path. This would allow error reports to actually use the data of the partition saved here instead of the parent data.
Good catch. I had missed this. It is added in v17.
Nathan
Attachments:
error_on_duplicate_columns_in_analyze_v4.patchapplication/octet-stream; name=error_on_duplicate_columns_in_analyze_v4.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d46e686e6c..c1ff4d7e7f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -71,7 +71,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
-static void check_columns_exist(List *relations);
+static void check_column_lists(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -266,8 +266,11 @@ vacuum(int options, List *relations, VacuumParams *params,
* Check that all specified columns exist so that we can fast-fail
* commands with multiple tables. If the column disappears before we
* actually process it, we will emit a WARNING and skip it later on.
+ *
+ * Also verify that there are no duplicate columns specified in any of
+ * the column lists.
*/
- check_columns_exist(relations);
+ check_column_lists(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -550,16 +553,20 @@ get_rel_oids(List **vacrels)
}
/*
- * Check that all specified columns for the relations exist.
+ * Check that all specified columns for the relations exist and that there are
+ * no duplicate columns specified in each list.
*
* This function emits an ERROR if the relation or the columns specified for
* a relation cannot be found. This is used to pre-validate any column lists
* specified in VACUUM/ANALYZE commands. If any columns disappear between
* calling this function and when we actually get to processing them, we will
* emit a WARNING and skip it at that time.
+ *
+ * This function also emits an ERROR if any column list contains duplicate
+ * entries, as ANALYZE will fail in that case.
*/
static void
-check_columns_exist(List *relations)
+check_column_lists(List *relations)
{
ListCell *relation_lc;
foreach(relation_lc, relations)
@@ -568,6 +575,7 @@ check_columns_exist(List *relations)
Relation rel;
ListCell *column_lc;
char *schemaname;
+ List *unique_columns;
if (relation->va_cols == NIL) /* nothing to check */
continue;
@@ -596,6 +604,9 @@ check_columns_exist(List *relations)
schemaname = get_namespace_name(RelationGetNamespace(rel));
+ /*
+ * First, verify that all specified columns exist.
+ */
foreach(column_lc, relation->va_cols)
{
char *col = strVal(lfirst(column_lc));
@@ -615,6 +626,29 @@ check_columns_exist(List *relations)
errmsg("column \%s\" of relation \"%s\" does not exist",
col, RelationGetRelationName(rel))));
}
+
+ /*
+ * Verify that there are no duplicate entries in the column list.
+ *
+ * We do this after checking that all columns exist so that nonexistent
+ * columns ERROR with higher priority than duplicates.
+ */
+ unique_columns = list_concat_unique(NIL, relation->va_cols);
+ if (list_length(unique_columns) != list_length(relation->va_cols))
+ {
+ if (schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s.%s\" contains duplicates",
+ schemaname, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s\" contains duplicates",
+ RelationGetRelationName(rel))));
+ }
relation_close(rel, NoLock);
}
}
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 62c1a03685..4272d1136c 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -97,6 +97,8 @@ VACUUM (FREEZE) vaccluster, does_not_exist;
ERROR: relation "does_not_exist" does not exist
VACUUM (FREEZE, ANALYZE) vactst (i);
VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.vactst" contains duplicates
VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
@@ -113,12 +115,19 @@ HINT: A column list was specified for relation "vaccluster".
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
ERROR: ANALYZE option must be specified when a column list is provided
HINT: A column list was specified for relation "vactst".
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.vactst" contains duplicates
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vactst" does not exist
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 10e0de0113..6ccba3b8c5 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -87,11 +87,14 @@ VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
VACUUM vactst (i);
VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
DROP TABLE vaccluster;
DROP TABLE vactst;
vacuum_multiple_tables_v17.patchapplication/octet-stream; name=vacuum_multiple_tables_v17.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee101df..c69a3490bf 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d117..3a0afa3b74 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e96b..e9cfd903d2 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
@@ -152,7 +154,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +397,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa181207a..7fca813594 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +48,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +70,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +94,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +122,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +130,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +145,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +196,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +249,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +335,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +356,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +411,105 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +527,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +545,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +560,77 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+ char *schemaname;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ continue;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ schemaname = get_namespace_name(RelationGetNamespace(rel));
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i != InvalidAttrNumber)
+ continue;
+
+ if (schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s.%s\" does not exist",
+ col, schemaname, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1232,6 +1389,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1309,6 +1467,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14e2b..d32c4e688b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b9146a..1d0d779609 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039da9..60d6addf0f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818c9b..b3fe50efb6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0a9d..b171049e9b 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a9035112e9..7a7b793ddf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1817..12d78ff923 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3363..ffeeb4919b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69753..2adbd54dd8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0478a8ac60..506ff98f22 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1282,13 +1282,13 @@ ERROR: "myview" is not a table, composite type, or foreign table
drop view myview;
-- test some commands to make sure they fail on the dropped column
analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
vacuum analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
vacuum analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
comment on column atacc1.a is 'testing';
ERROR: column "a" of relation "atacc1" does not exist
comment on column atacc1."........pg.dropped.1........" is 'testing';
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663087..62c1a03685 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,37 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04917..10e0de0113 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,29 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
Sorry for the spam. I am re-sending these patches with modified names so that
the apply order is obvious to the new automated testing framework (and to
everybody else).
Nathan
Attachments:
0002-error_on_duplicate_columns_in_analyze_v4.patchapplication/octet-stream; name=0002-error_on_duplicate_columns_in_analyze_v4.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d46e686e6c..c1ff4d7e7f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -71,7 +71,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
-static void check_columns_exist(List *relations);
+static void check_column_lists(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -266,8 +266,11 @@ vacuum(int options, List *relations, VacuumParams *params,
* Check that all specified columns exist so that we can fast-fail
* commands with multiple tables. If the column disappears before we
* actually process it, we will emit a WARNING and skip it later on.
+ *
+ * Also verify that there are no duplicate columns specified in any of
+ * the column lists.
*/
- check_columns_exist(relations);
+ check_column_lists(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -550,16 +553,20 @@ get_rel_oids(List **vacrels)
}
/*
- * Check that all specified columns for the relations exist.
+ * Check that all specified columns for the relations exist and that there are
+ * no duplicate columns specified in each list.
*
* This function emits an ERROR if the relation or the columns specified for
* a relation cannot be found. This is used to pre-validate any column lists
* specified in VACUUM/ANALYZE commands. If any columns disappear between
* calling this function and when we actually get to processing them, we will
* emit a WARNING and skip it at that time.
+ *
+ * This function also emits an ERROR if any column list contains duplicate
+ * entries, as ANALYZE will fail in that case.
*/
static void
-check_columns_exist(List *relations)
+check_column_lists(List *relations)
{
ListCell *relation_lc;
foreach(relation_lc, relations)
@@ -568,6 +575,7 @@ check_columns_exist(List *relations)
Relation rel;
ListCell *column_lc;
char *schemaname;
+ List *unique_columns;
if (relation->va_cols == NIL) /* nothing to check */
continue;
@@ -596,6 +604,9 @@ check_columns_exist(List *relations)
schemaname = get_namespace_name(RelationGetNamespace(rel));
+ /*
+ * First, verify that all specified columns exist.
+ */
foreach(column_lc, relation->va_cols)
{
char *col = strVal(lfirst(column_lc));
@@ -615,6 +626,29 @@ check_columns_exist(List *relations)
errmsg("column \%s\" of relation \"%s\" does not exist",
col, RelationGetRelationName(rel))));
}
+
+ /*
+ * Verify that there are no duplicate entries in the column list.
+ *
+ * We do this after checking that all columns exist so that nonexistent
+ * columns ERROR with higher priority than duplicates.
+ */
+ unique_columns = list_concat_unique(NIL, relation->va_cols);
+ if (list_length(unique_columns) != list_length(relation->va_cols))
+ {
+ if (schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s.%s\" contains duplicates",
+ schemaname, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s\" contains duplicates",
+ RelationGetRelationName(rel))));
+ }
relation_close(rel, NoLock);
}
}
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 62c1a03685..4272d1136c 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -97,6 +97,8 @@ VACUUM (FREEZE) vaccluster, does_not_exist;
ERROR: relation "does_not_exist" does not exist
VACUUM (FREEZE, ANALYZE) vactst (i);
VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.vactst" contains duplicates
VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
@@ -113,12 +115,19 @@ HINT: A column list was specified for relation "vaccluster".
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
ERROR: ANALYZE option must be specified when a column list is provided
HINT: A column list was specified for relation "vactst".
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.vactst" contains duplicates
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vactst" does not exist
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 10e0de0113..6ccba3b8c5 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -87,11 +87,14 @@ VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
VACUUM vactst (i);
VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
DROP TABLE vaccluster;
DROP TABLE vactst;
0001-vacuum_multiple_tables_v17.patchapplication/octet-stream; name=0001-vacuum_multiple_tables_v17.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee101df..c69a3490bf 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d117..3a0afa3b74 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e96b..e9cfd903d2 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
@@ -152,7 +154,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +397,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa181207a..7fca813594 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +48,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +70,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +94,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +122,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +130,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +145,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +196,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +249,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
* Build list of relations to process, unless caller gave us one. (If we
* build one, we put it in vac_context for safekeeping.)
*/
- relations = get_rel_oids(relid, relation);
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +335,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +356,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +411,105 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +527,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +545,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +560,77 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+ char *schemaname;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ continue;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ schemaname = get_namespace_name(RelationGetNamespace(rel));
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+ int i = attnameAttNum(rel, col, false);
+
+ if (i != InvalidAttrNumber)
+ continue;
+
+ if (schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s.%s\" does not exist",
+ col, schemaname, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1232,6 +1389,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1309,6 +1467,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14e2b..d32c4e688b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b9146a..1d0d779609 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039da9..60d6addf0f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818c9b..b3fe50efb6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0a9d..b171049e9b 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a9035112e9..7a7b793ddf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1817..12d78ff923 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3363..ffeeb4919b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69753..2adbd54dd8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0478a8ac60..506ff98f22 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1282,13 +1282,13 @@ ERROR: "myview" is not a table, composite type, or foreign table
drop view myview;
-- test some commands to make sure they fail on the dropped column
analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
vacuum analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
vacuum analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
comment on column atacc1.a is 'testing';
ERROR: column "a" of relation "atacc1" does not exist
comment on column atacc1."........pg.dropped.1........" is 'testing';
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663087..62c1a03685 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,37 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04917..10e0de0113 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,29 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
On Wed, Sep 13, 2017 at 12:31 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Sorry for the spam. I am re-sending these patches with modified names so that
the apply order is obvious to the new automated testing framework (and to
everybody else).
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
[...]
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* single table to process */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
We lose a bit of information here. I think that it would be good to
mention in the declaration of VacuumRelation that the RangeVar is used
for error processing, and needs to be filled. I have complained about
that upthread already, perhaps this has slipped away when rebasing.
+ int i = attnameAttNum(rel, col, false);
+
+ if (i != InvalidAttrNumber)
+ continue;
Nit: allocating "i" makes little sense here. You are not using it for
any other checks.
/*
- * Build a list of Oids for each relation to be processed
+ * Determine the OID for each relation to be processed
*
* The list is built in vac_context so that it will survive across our
* per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
Yeah, that's not completely correct either. This would be more like
"Fill in the list of VacuumRelation entries with their corresponding
OIDs, adding extra entries for partitioned tables".
Those are minor points. The patch seems to be in good shape, and
passes all my tests, including some pgbench'ing to make sure that
nothing goes weird. So I'll be happy to finally switch both patches to
"ready for committer" once those minor points are addressed.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/12/17, 9:47 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
- * relid, if not InvalidOid, indicate the relation to process; otherwise, - * the RangeVar is used. (The latter must always be passed, because it's - * used for error messages.) [...] +typedef struct VacuumRelation +{ + NodeTag type; + RangeVar *relation; /* single table to process */ + List *va_cols; /* list of column names, or NIL for all */ + Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */ +} VacuumRelation; We lose a bit of information here. I think that it would be good to mention in the declaration of VacuumRelation that the RangeVar is used for error processing, and needs to be filled. I have complained about that upthread already, perhaps this has slipped away when rebasing.
I've added a comment to this effect in v18 of the main patch.
+ int i = attnameAttNum(rel, col, false); + + if (i != InvalidAttrNumber) + continue; Nit: allocating "i" makes little sense here. You are not using it for any other checks.
True. I've removed "i" in v18.
/* - * Build a list of Oids for each relation to be processed + * Determine the OID for each relation to be processed * * The list is built in vac_context so that it will survive across our * per-relation transactions. */ -static List * -get_rel_oids(Oid relid, const RangeVar *vacrel) +static void +get_rel_oids(List **vacrels) Yeah, that's not completely correct either. This would be more like "Fill in the list of VacuumRelation entries with their corresponding OIDs, adding extra entries for partitioned tables".
I've added some more accurate comments for get_rel_oids() in v18.
Those are minor points. The patch seems to be in good shape, and
passes all my tests, including some pgbench'ing to make sure that
nothing goes weird. So I'll be happy to finally switch both patches to
"ready for committer" once those minor points are addressed.
Awesome.
Nathan
Attachments:
0002-error_on_duplicate_columns_in_analyze_v5.patchapplication/octet-stream; name=0002-error_on_duplicate_columns_in_analyze_v5.patchDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 2fb73ce..345050f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -71,7 +71,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static void get_rel_oids(List **vacrels);
-static void check_columns_exist(List *relations);
+static void check_column_lists(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -269,8 +269,11 @@ vacuum(int options, List *relations, VacuumParams *params,
* Check that all specified columns exist so that we can fast-fail
* commands with multiple tables. If the column disappears before we
* actually process it, we will emit a WARNING and skip it later on.
+ *
+ * Also verify that there are no duplicate columns specified in any of
+ * the column lists.
*/
- check_columns_exist(relations);
+ check_column_lists(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -568,16 +571,20 @@ get_rel_oids(List **vacrels)
}
/*
- * Check that all specified columns for the relations exist.
+ * Check that all specified columns for the relations exist and that there are
+ * no duplicate columns specified in each list.
*
* This function emits an ERROR if the relation or the columns specified for
* a relation cannot be found. This is used to pre-validate any column lists
* specified in VACUUM/ANALYZE commands. If any columns disappear between
* calling this function and when we actually get to processing them, we will
* emit a WARNING and skip it at that time.
+ *
+ * This function also emits an ERROR if any column list contains duplicate
+ * entries, as ANALYZE will fail in that case.
*/
static void
-check_columns_exist(List *relations)
+check_column_lists(List *relations)
{
ListCell *relation_lc;
foreach(relation_lc, relations)
@@ -586,6 +593,7 @@ check_columns_exist(List *relations)
Relation rel;
ListCell *column_lc;
char *schemaname;
+ List *unique_columns;
if (relation->va_cols == NIL) /* nothing to check */
continue;
@@ -614,6 +622,9 @@ check_columns_exist(List *relations)
schemaname = get_namespace_name(RelationGetNamespace(rel));
+ /*
+ * First, verify that all specified columns exist.
+ */
foreach(column_lc, relation->va_cols)
{
char *col = strVal(lfirst(column_lc));
@@ -632,6 +643,29 @@ check_columns_exist(List *relations)
errmsg("column \"%s\" of relation \"%s\" does not exist",
col, RelationGetRelationName(rel))));
}
+
+ /*
+ * Verify that there are no duplicate entries in the column list.
+ *
+ * We do this after checking that all columns exist so that nonexistent
+ * columns ERROR with higher priority than duplicates.
+ */
+ unique_columns = list_concat_unique(NIL, relation->va_cols);
+ if (list_length(unique_columns) != list_length(relation->va_cols))
+ {
+ if (schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s.%s\" contains duplicates",
+ schemaname, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s\" contains duplicates",
+ RelationGetRelationName(rel))));
+ }
relation_close(rel, NoLock);
}
}
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 62c1a03..4272d11 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -97,6 +97,8 @@ VACUUM (FREEZE) vaccluster, does_not_exist;
ERROR: relation "does_not_exist" does not exist
VACUUM (FREEZE, ANALYZE) vactst (i);
VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.vactst" contains duplicates
VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
@@ -113,12 +115,19 @@ HINT: A column list was specified for relation "vaccluster".
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
ERROR: ANALYZE option must be specified when a column list is provided
HINT: A column list was specified for relation "vactst".
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.vactst" contains duplicates
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vactst" does not exist
DROP TABLE vaccluster;
DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 10e0de0..6ccba3b 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -87,11 +87,14 @@ VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
VACUUM vactst (i);
VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (ANALYZE) vactst, vacparted (does_not_exist, does_not_exist);
ANALYZE vactst, vacparted;
ANALYZE vacparted (b), vactst;
ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
ANALYZE vactst, vactst, vactst;
ANALYZE vacparted (a), vacparted (b), vacparted;
+ANALYZE vactst (i, i), vacparted (a), vactst (i), vacparted (a, b, a);
+ANALYZE vacparted, vactst (i, i, i, does_not_exist);
DROP TABLE vaccluster;
DROP TABLE vactst;
0001-vacuum_multiple_tables_v18.patchapplication/octet-stream; name=0001-vacuum_multiple_tables_v18.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e..e9cfd90 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
@@ -152,7 +154,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
@@ -383,12 +397,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
{
char *col = strVal(lfirst(le));
+ /*
+ * We have already checked the column list in vacuum(...),
+ * but the columns may have disappeared since then. If
+ * this happens, emit a nice warning message and skip the
+ * undefined column.
+ */
i = attnameAttNum(onerel, col, false);
if (i == InvalidAttrNumber)
- ereport(ERROR,
+ {
+ ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
+ errmsg("skipping column \"%s\" of relation \"%s\" because the column no longer exists",
col, RelationGetRelationName(onerel))));
+ continue;
+ }
+
vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
if (vacattrstats[tcnt] != NULL)
tcnt++;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..2fb73ce 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,8 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +48,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +70,8 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
+static void check_columns_exist(List *relations);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +94,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +122,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +130,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +145,15 @@ 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)
{
- const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
- List *relations;
- static bool in_vacuum = false;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+ static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +196,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +249,28 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
- * Build list of relations to process, unless caller gave us one. (If we
- * build one, we put it in vac_context for safekeeping.)
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
*/
- relations = get_rel_oids(relid, relation);
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Fill in the list of relations with their corresponding OIDs.
+ *
+ * If a listed relation is a partitioned table, an extra entry will be
+ * added to the list for each partition. The list is kept in
+ * vac_context for safekeeping.
+ */
+ get_rel_oids(&relations);
+
+ /*
+ * Check that all specified columns exist so that we can fast-fail
+ * commands with multiple tables. If the column disappears before we
+ * actually process it, we will emit a WARNING and skip it later on.
+ */
+ check_columns_exist(relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +338,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +359,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +414,106 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Fill in the list of relations with their corresponding OIDs.
*
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * If a listed relation is a partitioned table, an extra entry will be added to
+ * the list for each partition. The list is kept in vac_context so that it will
+ * survive across our per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +531,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +549,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +564,76 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
+}
+
+/*
+ * Check that all specified columns for the relations exist.
+ *
+ * This function emits an ERROR if the relation or the columns specified for
+ * a relation cannot be found. This is used to pre-validate any column lists
+ * specified in VACUUM/ANALYZE commands. If any columns disappear between
+ * calling this function and when we actually get to processing them, we will
+ * emit a WARNING and skip it at that time.
+ */
+static void
+check_columns_exist(List *relations)
+{
+ ListCell *relation_lc;
+ foreach(relation_lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, relation_lc);
+ Relation rel;
+ ListCell *column_lc;
+ char *schemaname;
+
+ if (relation->va_cols == NIL) /* nothing to check */
+ continue;
+
+ /*
+ * Since we are only checking the existence of columns,
+ * we do not take a lock yet. If a relation or column
+ * disappears before we get to it, we will emit a
+ * WARNING and skip it at that time.
+ */
+ rel = try_relation_open(relation->oid, NoLock);
+ if (!rel)
+ {
+ if (relation->relation->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s.%s\" does not exist",
+ relation->relation->schemaname,
+ relation->relation->relname)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ relation->relation->relname)));
+ }
+
+ schemaname = get_namespace_name(RelationGetNamespace(rel));
+
+ foreach(column_lc, relation->va_cols)
+ {
+ char *col = strVal(lfirst(column_lc));
+
+ if (attnameAttNum(rel, col, false) != InvalidAttrNumber)
+ continue;
+
+ if (schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s.%s\" does not exist",
+ col, schemaname, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ col, RelationGetRelationName(rel))));
+ }
+ relation_close(rel, NoLock);
+ }
}
/*
@@ -1232,6 +1392,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1309,6 +1470,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14..d32c4e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b91..1d0d779 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818..b3fe50e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..b171049 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69..9fdba9e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* table to process (required for error messaging) */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0478a8a..506ff98 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1282,13 +1282,13 @@ ERROR: "myview" is not a table, composite type, or foreign table
drop view myview;
-- test some commands to make sure they fail on the dropped column
analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
vacuum analyze atacc1(a);
-ERROR: column "a" of relation "atacc1" does not exist
+ERROR: column "a" of relation "public.atacc1" does not exist
vacuum analyze atacc1("........pg.dropped.1........");
-ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
+ERROR: column "........pg.dropped.1........" of relation "public.atacc1" does not exist
comment on column atacc1.a is 'testing';
ERROR: column "a" of relation "atacc1" does not exist
comment on column atacc1."........pg.dropped.1........" is 'testing';
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..62c1a03 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,4 +88,37 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "public.vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..10e0de0 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,4 +70,29 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (VERBOSE, ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM (VERBOSE) vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE VERBOSE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
Hello, I began to look on this. (But it seems almost ready for committer..)
At Wed, 13 Sep 2017 11:47:11 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqTYbJRU14SG0qwueTLbZHutZ8OWCV0L9NiK1MQ_nzqCkw@mail.gmail.com>
On Wed, Sep 13, 2017 at 12:31 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Sorry for the spam. I am re-sending these patches with modified names so that
the apply order is obvious to the new automated testing framework (and to
everybody else).- * relid, if not InvalidOid, indicate the relation to process; otherwise, - * the RangeVar is used. (The latter must always be passed, because it's - * used for error messages.) [...] +typedef struct VacuumRelation +{ + NodeTag type; + RangeVar *relation; /* single table to process */ + List *va_cols; /* list of column names, or NIL for all */ + Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */ +} VacuumRelation; We lose a bit of information here. I think that it would be good to mention in the declaration of VacuumRelation that the RangeVar is used for error processing, and needs to be filled. I have complained about that upthread already, perhaps this has slipped away when rebasing.+ int i = attnameAttNum(rel, col, false); + + if (i != InvalidAttrNumber) + continue; Nit: allocating "i" makes little sense here. You are not using it for any other checks./* - * Build a list of Oids for each relation to be processed + * Determine the OID for each relation to be processed * * The list is built in vac_context so that it will survive across our * per-relation transactions. */ -static List * -get_rel_oids(Oid relid, const RangeVar *vacrel) +static void +get_rel_oids(List **vacrels) Yeah, that's not completely correct either. This would be more like "Fill in the list of VacuumRelation entries with their corresponding OIDs, adding extra entries for partitioned tables".Those are minor points. The patch seems to be in good shape, and
passes all my tests, including some pgbench'ing to make sure that
nothing goes weird. So I'll be happy to finally switch both patches to
"ready for committer" once those minor points are addressed.
May I ask one question?
This patch creates a new memory context "Vacuum" under
PortalContext in vacuum.c, but AFAICS the current context there
is PortalHeapMemory, which has the same expected lifetime with
the new context (that is, a child of PotalContext and dropeed in
PortalDrop). On the other hand the PortalMemory's lifetime is not
PortalStart to PortaDrop but the backend lifetime (initialized in
InitPostgres).
/*
* Create special memory context for cross-transaction storage.
*
* Since it is a child of PortalContext, it will go away eventually even
* if we suffer an error; there's no need for special abort cleanup logic.
*/
vac_context = AllocSetContextCreate(PortalContext,
"Vacuum",
ALLOCSET_DEFAULT_SIZES);
So this seems to work as opposite to the expectation. Am I
missing something?
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Sep 13, 2017 at 1:13 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:
This patch creates a new memory context "Vacuum" under
PortalContext in vacuum.c, but AFAICS the current context there
is PortalHeapMemory, which has the same expected lifetime with
the new context (that is, a child of PotalContext and dropeed in
PortalDrop). On the other hand the PortalMemory's lifetime is not
PortalStart to PortaDrop but the backend lifetime (initialized in
InitPostgres).
Which patch are you looking to? This introduces no new memory context,
be it in 0001 or 0002 in its last versions. I don't recall during the
successive reviews seeing that pattern as well.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Sep 13, 2017 at 1:12 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 9/12/17, 9:47 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
Those are minor points. The patch seems to be in good shape, and
passes all my tests, including some pgbench'ing to make sure that
nothing goes weird. So I'll be happy to finally switch both patches to
"ready for committer" once those minor points are addressed.Awesome.
OK, let's roll in then. There are a couple of points or comments that
could be tweaked like the name of get_rel_oids, but I usually have bad
taste when it comes to that, and the current things make sense as
well. So both patches are now marked as ready for committer.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
At Wed, 13 Sep 2017 13:16:52 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqSi7N1dVk=sYxoBj-Arkri341ydNO5rdnoCfo1sXmbv_A@mail.gmail.com>
On Wed, Sep 13, 2017 at 1:13 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:This patch creates a new memory context "Vacuum" under
PortalContext in vacuum.c, but AFAICS the current context there
is PortalHeapMemory, which has the same expected lifetime with
the new context (that is, a child of PotalContext and dropeed in
PortalDrop). On the other hand the PortalMemory's lifetime is not
PortalStart to PortaDrop but the backend lifetime (initialized in
InitPostgres).Which patch are you looking to? This introduces no new memory context,
be it in 0001 or 0002 in its last versions. I don't recall during the
successive reviews seeing that pattern as well.
Sorry. 0001 patch is just using the context. The context has been
introduced by the commit e415b469 and used only for buffer access
strategy object. I was confused by the following comment in the
patch.
| + * Move our relation list to our special memory context so that we do
| + * not lose it among our per-relation transactions.
The context exists there before the patch but anyway using the
context as per-portal context that doesn't need freeing seems to
result in memory leak.
regrds,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello,
At Wed, 13 Sep 2017 17:28:20 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20170913.172820.141647434.horiguchi.kyotaro@lab.ntt.co.jp>
The context exists there before the patch but anyway using the
context as per-portal context that doesn't need freeing seems to
result in memory leak.
It is released at the end of vacuum.
So it's no problem.
Sorry for the noise.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"Bossart, Nathan" <bossartn@amazon.com> writes:
[ 0001-vacuum_multiple_tables_v18.patch ]
I started to look at this, but soon became pretty desperately unhappy with
the way that it makes a bunch of tests on the relations and then releases
all locks on them. That either makes the tests pointless, or requires you
to invent completely bizarre semantics, like this:
* Check that all specified columns exist so that we can fast-fail
* commands with multiple tables. If the column disappears before we
* actually process it, we will emit a WARNING and skip it later on.
I think that the way this ought to work is you process the VacuumRelation
structs purely sequentially, each in its own transaction, so you don't
need leaps of faith about what to do if a relation changes between the
first time you look at it and when you actually come to process it.
The idea of "fast failing" because some later VacuumRelation struct might
contain an error seems like useless complication, both in terms of the
implementation and the user-visible behavior.
It looks like some of this stuff might be the fault of the
partitioned-tables patch more than your own changes, but no time like
the present to try to improve matters.
As for the other patch, I wonder if it might not be better to
silently ignore duplicate column names. But in either case, we don't
need a pre-check, IMO. I'd just leave it to the lookup loop in
do_analyze_rel to notice if it's looked up the same column number
twice. That would be more likely to lead to a back-patchable fix,
too. 0002 as it stands is useless as a back-patch basis, since it
proposes modifying code that doesn't even exist in the back branches.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/20/17, 2:29 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:
I started to look at this...
Thanks for taking a look.
I think that the way this ought to work is you process the VacuumRelation
structs purely sequentially, each in its own transaction, so you don't
need leaps of faith about what to do if a relation changes between the
first time you look at it and when you actually come to process it.
How might this work for VACUUM statements with no tables specified? It
seems like we must either handle the case when the relation changes before
it is processed or hold a lock for the duration of the vacuuming.
The idea of "fast failing" because some later VacuumRelation struct might
contain an error seems like useless complication, both in terms of the
implementation and the user-visible behavior.
I agree that the patch might be simpler without this, but the user-visible
behavior is the reason I had included it. In short, my goal was to avoid
errors halfway through a long-running VACUUM statement because the user
misspelled a relation/column name or the relation/column was dropped.
It's true that the tests become mostly pointless if the relations or
columns change before they are processed, but this seems like it would be
a rarer issue in typical use cases.
I'll continue to look into switching to a more sequential approach and
eliminating the leaps of faith.
As for the other patch, I wonder if it might not be better to
silently ignore duplicate column names. But in either case, we don't
need a pre-check, IMO. I'd just leave it to the lookup loop in
do_analyze_rel to notice if it's looked up the same column number
twice. That would be more likely to lead to a back-patchable fix,
too. 0002 as it stands is useless as a back-patch basis, since it
proposes modifying code that doesn't even exist in the back branches.
I'd be happy to write something up that is more feasibly back-patched.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"Bossart, Nathan" <bossartn@amazon.com> writes:
On 9/20/17, 2:29 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:
I think that the way this ought to work is you process the VacuumRelation
structs purely sequentially, each in its own transaction, so you don't
need leaps of faith about what to do if a relation changes between the
first time you look at it and when you actually come to process it.
How might this work for VACUUM statements with no tables specified? It
seems like we must either handle the case when the relation changes before
it is processed or hold a lock for the duration of the vacuuming.
I don't see a need to change the way that vacuum-with-no-table operates.
It essentially collects a list of relation OIDs that exist at the start
of vacuuming, and then vacuums all the ones that still exist when their
turn comes. Since no information beyond the bare OID is saved across the
preceding transactions, there's not really any schema-change risk involved.
We could possibly adapt that concept to the inheritance/partitioning cases
for vacuum with a table name or list of names: when we first come to a
VacuumRelation, collect a list of child table OIDs, and then process each
one unless it's disappeared by the time its turn comes. But in any case,
we should not be doing any checks on a particular relation until we've got
it open and locked with intent to vacuum.
The idea of "fast failing" because some later VacuumRelation struct might
contain an error seems like useless complication, both in terms of the
implementation and the user-visible behavior.
I agree that the patch might be simpler without this, but the user-visible
behavior is the reason I had included it. In short, my goal was to avoid
errors halfway through a long-running VACUUM statement because the user
misspelled a relation/column name or the relation/column was dropped.
I don't particularly buy that argument, because it's not the case that
the preceding processing was wasted when that happens. We've done and
committed the vacuuming work for the earlier relations.
It's true that the tests become mostly pointless if the relations or
columns change before they are processed, but this seems like it would be
a rarer issue in typical use cases.
Nonetheless, we'd have to explain this behavior to people, and I think
it's mostly useless complication. With what I'm proposing, if vacuum
complains about the third table in the list, you know it has done the
ones before that. What what you want to do, maybe it did the ones
before that, or maybe it didn't.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 21, 2017 at 8:18 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Bossart, Nathan" <bossartn@amazon.com> writes:
On 9/20/17, 2:29 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:
The idea of "fast failing" because some later VacuumRelation struct might
contain an error seems like useless complication, both in terms of the
implementation and the user-visible behavior.I agree that the patch might be simpler without this, but the user-visible
behavior is the reason I had included it. In short, my goal was to avoid
errors halfway through a long-running VACUUM statement because the user
misspelled a relation/column name or the relation/column was dropped.I don't particularly buy that argument, because it's not the case that
the preceding processing was wasted when that happens. We've done and
committed the vacuuming work for the earlier relations.
I think that the problem can be seen differently though: the next
relations on the list would not be processed as well. For example in
parallel of a manual VACUUM triggered by a cron job, say that a rogue
admin removes a column for a relation to be VACUUM-ed. The relations
processed before the relation redefined would have been vacuumed and
the transaction doing the vacuum committed, but the ones listed after
would not have been updated in this nightly VACUUM. From this angle
this sounds like a fair concern to me. I agree that the rogue admin
should not have done that to begin with.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael Paquier <michael.paquier@gmail.com> writes:
On Thu, Sep 21, 2017 at 8:18 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Bossart, Nathan" <bossartn@amazon.com> writes:
I agree that the patch might be simpler without this, but the user-visible
behavior is the reason I had included it. In short, my goal was to avoid
errors halfway through a long-running VACUUM statement because the user
misspelled a relation/column name or the relation/column was dropped.
I don't particularly buy that argument, because it's not the case that
the preceding processing was wasted when that happens. We've done and
committed the vacuuming work for the earlier relations.
I think that the problem can be seen differently though: the next
relations on the list would not be processed as well. For example in
parallel of a manual VACUUM triggered by a cron job, say that a rogue
admin removes a column for a relation to be VACUUM-ed. The relations
processed before the relation redefined would have been vacuumed and
the transaction doing the vacuum committed, but the ones listed after
would not have been updated in this nightly VACUUM.
Um ... so? With Nathan's proposed behavior, there are two cases depending
on just when the unexpected schema change happens:
1. *None* of the work gets done.
2. The work before the troublesome relation gets done, and the work after
doesn't.
I think it'll be much easier to understand if the behavior is always (2).
And I don't see any particular advantage to (1) anyway, especially not
for an unattended vacuum script.
Keep in mind that there were not-entirely-unjustified complaints upthread
about whether we needed to add any complexity here at all. I'd just as
soon keep the added complexity to a minimum, especially when it's in
service of behaviors that are not clearly improvements.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 21, 2017 at 9:08 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
On Thu, Sep 21, 2017 at 8:18 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Bossart, Nathan" <bossartn@amazon.com> writes:
I agree that the patch might be simpler without this, but the user-visible
behavior is the reason I had included it. In short, my goal was to avoid
errors halfway through a long-running VACUUM statement because the user
misspelled a relation/column name or the relation/column was dropped.I don't particularly buy that argument, because it's not the case that
the preceding processing was wasted when that happens. We've done and
committed the vacuuming work for the earlier relations.I think that the problem can be seen differently though: the next
relations on the list would not be processed as well. For example in
parallel of a manual VACUUM triggered by a cron job, say that a rogue
admin removes a column for a relation to be VACUUM-ed. The relations
processed before the relation redefined would have been vacuumed and
the transaction doing the vacuum committed, but the ones listed after
would not have been updated in this nightly VACUUM.Um ... so? With Nathan's proposed behavior, there are two cases depending
on just when the unexpected schema change happens:1. *None* of the work gets done.
2. The work before the troublesome relation gets done, and the work after
doesn't.I think it'll be much easier to understand if the behavior is always (2).
And I don't see any particular advantage to (1) anyway, especially not
for an unattended vacuum script.
You may be missing one which is closer to what autovacuum does:
3) Issue a warning for the troublesome relation, and get the work done
a maximum.
Keep in mind that there were not-entirely-unjustified complaints upthread
about whether we needed to add any complexity here at all. I'd just as
soon keep the added complexity to a minimum, especially when it's in
service of behaviors that are not clearly improvements.
Yeah, I have sympathy for that argument as well. At some point during
the review I am sure that I complained about such things :)
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael Paquier <michael.paquier@gmail.com> writes:
On Thu, Sep 21, 2017 at 9:08 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Um ... so? With Nathan's proposed behavior, there are two cases depending
on just when the unexpected schema change happens:
1. *None* of the work gets done.
2. The work before the troublesome relation gets done, and the work after
doesn't.
You may be missing one which is closer to what autovacuum does:
3) Issue a warning for the troublesome relation, and get the work done
a maximum.
Well, we could certainly discuss whether the behavior on detecting a
conflict ought to be "error" or "warning and continue". But I do not buy
the value of "it might be one or the other depending on timing".
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 21, 2017 at 9:51 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
On Thu, Sep 21, 2017 at 9:08 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Um ... so? With Nathan's proposed behavior, there are two cases depending
on just when the unexpected schema change happens:
1. *None* of the work gets done.
2. The work before the troublesome relation gets done, and the work after
doesn't.You may be missing one which is closer to what autovacuum does:
3) Issue a warning for the troublesome relation, and get the work done
a maximum.Well, we could certainly discuss whether the behavior on detecting a
conflict ought to be "error" or "warning and continue". But I do not buy
the value of "it might be one or the other depending on timing".
I definitely agree with that, and I don't want this point to be a
blocker for the proposed patch either. So if you feel that for now the
focus should be on simplicity rather than reliability (my word may be
incorrect here, I mean having a consistent and continuous flow), let's
do so then. We could always change the implemented behavior later on.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/20/17, 7:58 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
On Thu, Sep 21, 2017 at 9:51 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
On Thu, Sep 21, 2017 at 9:08 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Um ... so? With Nathan's proposed behavior, there are two cases depending
on just when the unexpected schema change happens:
1. *None* of the work gets done.
2. The work before the troublesome relation gets done, and the work after
doesn't.You may be missing one which is closer to what autovacuum does:
3) Issue a warning for the troublesome relation, and get the work done
a maximum.Well, we could certainly discuss whether the behavior on detecting a
conflict ought to be "error" or "warning and continue". But I do not buy
the value of "it might be one or the other depending on timing".I definitely agree with that, and I don't want this point to be a
blocker for the proposed patch either. So if you feel that for now the
focus should be on simplicity rather than reliability (my word may be
incorrect here, I mean having a consistent and continuous flow), let's
do so then. We could always change the implemented behavior later on.
Alright, here is something that I think is more like what Tom envisioned.
The duplicate column checks have been moved to do_analyze_rel(), and I am
hoping that it is more feasible to back-patch. The main path now
processes each specified relation individually, which admittedly makes the
patch a bit simpler.
Nathan
Attachments:
0002-vacuum_multiple_tables_v19.patchapplication/octet-stream; name=0002-vacuum_multiple_tables_v19.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index a46ef0e..1e5a107 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
@@ -152,7 +154,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..7dc0648 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +47,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +69,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static List *get_rel_oids(VacuumRelation *vacrel);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +92,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +120,15 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ if (vacstmt->rels == NIL)
+ vacuum(vacstmt->options, NULL, ¶ms, NULL, isTopLevel);
+ else
+ {
+ ListCell *lc;
+ foreach(lc, vacstmt->rels)
+ vacuum(vacstmt->options, lfirst_node(VacuumRelation, lc),
+ ¶ms, NULL, isTopLevel);
+ }
}
/*
@@ -128,15 +136,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relation is the VacuumRelation to process. If it is NULL, all relations
+ * in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,8 +151,8 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
* memory context that will not disappear at transaction commit.
*/
void
-vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
- List *va_cols, BufferAccessStrategy bstrategy, bool isTopLevel)
+vacuum(int options, VacuumRelation *relation, VacuumParams *params,
+ BufferAccessStrategy bstrategy, bool isTopLevel)
{
const char *stmttype;
volatile bool in_outer_xact,
@@ -196,6 +201,25 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if a column list is present.
+ */
+ if (relation != NULL && relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +250,13 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
- * Build list of relations to process, unless caller gave us one. (If we
- * build one, we put it in vac_context for safekeeping.)
+ * Create a list of relations with their corresponding OIDs.
+ *
+ * If the relation is a partitioned table, an extra entry will be added
+ * to the list for each partition. The list is kept in vac_context for
+ * safekeeping.
*/
- relations = get_rel_oids(relid, relation);
+ relations = get_rel_oids(relation);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +324,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +345,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,22 +400,23 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Create a list of relations with their corresponding OIDs.
*
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * If the given relation is a partitioned table, an extra entry will be added to
+ * the list for each partition. The list is kept in vac_context so that it will
+ * survive across our per-relation transactions.
*/
static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+get_rel_oids(VacuumRelation *vacrel)
{
- List *oid_list = NIL;
+ List *vacrels = NIL;
MemoryContext oldcontext;
/* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (vacrel != NULL && OidIsValid(vacrel->oid))
{
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
+ vacrels = lappend(vacrels, vacrel);
MemoryContextSwitchTo(oldcontext);
}
else if (vacrel)
@@ -408,7 +436,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
* going to commit this transaction and begin a new one between now
* and then.
*/
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ relid = RangeVarGetRelid(vacrel->relation, NoLock, false);
/*
* To check whether the relation is a partitioned table, fetch its
@@ -428,11 +456,39 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
* lock to be taken to match what would be done otherwise.
*/
oldcontext = MemoryContextSwitchTo(vac_context);
+
if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, vacrel->va_cols, part_oid);
+
+ vacrels = lappend(vacrels, tmp);
+ }
+ }
else
- oid_list = lappend_oid(oid_list, relid);
+ {
+ vacrel->oid = relid;
+ vacrels = lappend(vacrels, vacrel);
+ }
+
MemoryContextSwitchTo(oldcontext);
}
else
@@ -451,7 +507,12 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
+ VacuumRelation *relinfo;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +526,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels = lappend(vacrels, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +541,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ return vacrels;
}
/*
@@ -1232,6 +1300,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1309,6 +1378,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14..d32c4e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b91..1d0d779 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818..b3fe50e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index b745d89..246e484 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,23 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..441ec4d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, VacuumRelation *relation, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69..9fdba9e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* table to process (required for error messaging) */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 99a7007..9726a86 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -90,6 +88,41 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+ERROR: relation "does_not_exist" does not exist
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.vactst" contains duplicates
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (ANALYZE) vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+VACUUM vactst, vacparted, vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vaccluster".
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vactst".
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE vactst (i), vacparted (does_not_exist);
+ERROR: column "does_not_exist" of relation "vacparted" does not exist
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
-- analyze with duplicate columns
CREATE TABLE analyze_test (a int, b char, c int, d char, e int, f char);
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 3d4e308..d4e59cf 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -73,6 +70,31 @@ UPDATE vacparted SET b = 'b';
VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
+-- multiple tables specified
+VACUUM FREEZE vaccluster, vactst;
+VACUUM FREEZE vacparted, does_not_exist;
+VACUUM (FREEZE) vacparted, vaccluster, vactst;
+VACUUM (FREEZE) vaccluster, does_not_exist;
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM (FREEZE, ANALYZE) vactst, vacparted (a);
+VACUUM (FREEZE, ANALYZE) vacparted, vactst (i), vacparted;
+VACUUM (FREEZE, ANALYZE) vactst (i), vacparted (a, b), vaccluster (i);
+VACUUM (ANALYZE) vactst (i), vacparted (does_not_exist);
+VACUUM FREEZE vactst, vactst, vactst;
+VACUUM (FREEZE, ANALYZE) vacparted (a), vacparted (b), vacparted;
+VACUUM vactst (i);
+VACUUM vactst, vacparted, vaccluster (i);
+VACUUM (VERBOSE) vactst (i), vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted;
+ANALYZE vacparted (b), vactst;
+ANALYZE vactst (i), vacparted (does_not_exist);
+ANALYZE vactst, vactst, vactst;
+ANALYZE vacparted (a), vacparted (b), vacparted;
+
+DROP TABLE vaccluster;
+DROP TABLE vactst;
DROP TABLE vacparted;
-- analyze with duplicate columns
0001-error_on_duplicate_columns_in_analyze_v6.patchapplication/octet-stream; name=0001-error_on_duplicate_columns_in_analyze_v6.patchDownload
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e..a46ef0e 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -375,6 +375,7 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
if (va_cols != NIL)
{
ListCell *le;
+ List *unique_columns;
vacattrstats = (VacAttrStats **) palloc(list_length(va_cols) *
sizeof(VacAttrStats *));
@@ -394,6 +395,22 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
tcnt++;
}
attr_cnt = tcnt;
+
+ /*
+ * Verify that there are no duplicate entries in the column list.
+ *
+ * We do this after checking that all columns exist so that
+ * nonexistent columns ERROR with higher priority than
+ * duplicates.
+ */
+ unique_columns = list_concat_unique(NIL, va_cols);
+ if (list_length(unique_columns) != list_length(va_cols))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column lists cannot have duplicate entries"),
+ errhint("the column list specified for relation \"%s.%s\" contains duplicates",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
}
else
{
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 6f68663..99a7007 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -91,3 +91,20 @@ VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
DROP TABLE vacparted;
+-- analyze with duplicate columns
+CREATE TABLE analyze_test (a int, b char, c int, d char, e int, f char);
+ANALYZE analyze_test (f, d, b);
+ANALYZE VERBOSE analyze_test (a, a, a, a);
+INFO: analyzing "public.analyze_test"
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.analyze_test" contains duplicates
+ANALYZE VERBOSE analyze_test (a, b, c, d, e, f, b);
+INFO: analyzing "public.analyze_test"
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.analyze_test" contains duplicates
+ANALYZE analyze_test;
+VACUUM FREEZE ANALYZE analyze_test (c, d, e, b, d);
+ERROR: column lists cannot have duplicate entries
+HINT: the column list specified for relation "public.analyze_test" contains duplicates
+VACUUM ANALYZE analyze_test;
+DROP TABLE analyze_test;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7c5fb04..3d4e308 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -74,3 +74,13 @@ VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
DROP TABLE vacparted;
+
+-- analyze with duplicate columns
+CREATE TABLE analyze_test (a int, b char, c int, d char, e int, f char);
+ANALYZE analyze_test (f, d, b);
+ANALYZE VERBOSE analyze_test (a, a, a, a);
+ANALYZE VERBOSE analyze_test (a, b, c, d, e, f, b);
+ANALYZE analyze_test;
+VACUUM FREEZE ANALYZE analyze_test (c, d, e, b, d);
+VACUUM ANALYZE analyze_test;
+DROP TABLE analyze_test;
"Bossart, Nathan" <bossartn@amazon.com> writes:
[ 0001-error_on_duplicate_columns_in_analyze_v6.patch ]
I've pushed (and back-patched) the 0001 patch, with some significant
changes:
* The list_concat_unique implementation is O(N^2), and seems a bit obscure
as well. Perhaps there will never be so many column list entries that
the speed is a big issue, but I'm not sure. I replaced the list with a
bitmap to avoid the risk.
* I did not see the point of having "ANALYZE t (x,x,x,nosuchcolumn)"
complain about nosuchcolumn rather than the duplicated x entries,
especially not if it meant you couldn't say which column was duplicated.
So I folded the test into the primary loop and made the error look
more like what you get for a nonexistent column.
* I didn't like the test case at all: it was repetitive and it actually
failed to exhibit the originally problematic behavior with unpatched
code, which is generally a minimum expectation for a regression test.
(I think that's because you used an empty table, for which ANALYZE will
not try to make any pg_statistic entries.) So I rewrote that, and made
it use an existing table rather than create a whole new one just for this.
I'll take a look at the updated 0002 tomorrow, but if anyone living in
a different timezone wants to review it meanwhile, feel free.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 22, 2017 at 7:26 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Bossart, Nathan" <bossartn@amazon.com> writes:
[ 0001-error_on_duplicate_columns_in_analyze_v6.patch ]
I've pushed (and back-patched) the 0001 patch, with some significant
changes:
Thanks. Arrived here too late to give feedback on the proposed patch.
I'll take a look at the updated 0002 tomorrow, but if anyone living in
a different timezone wants to review it meanwhile, feel free.
Here is some feedback. 0002 fails to apply after 0001 has been
committed in its regression tests, that can easily be resolved.
A couple of tests could be removed:
+VACUUM (FREEZE, ANALYZE) vactst (i);
+VACUUM (FREEZE, ANALYZE) vactst (i, i, i);
+VACUUM vactst (i);
void
-vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
- List *va_cols, BufferAccessStrategy bstrategy, bool isTopLevel)
+vacuum(int options, VacuumRelation *relation, VacuumParams *params,
+ BufferAccessStrategy bstrategy, bool isTopLevel)
[...]
- relations = get_rel_oids(relid, relation);
+ relations = get_rel_oids(relation);
I still think that ExecVacuum() should pass a list of VacuumRelation
objects to vacuum(), and get_rel_oids() should take in input a list,
and return a completed lists. This way the decision-making of doing
everything in the same transaction should happens once in vacuum().
And actually, if several relations are defined with VACUUM, your patch
would not use one transaction per table as use_own_xacts would be set
to false. I think that Tom meant that relations whose processed has
finished have to be committed immediately. Per your patch, the commit
happens once all relations are committed.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/21/17, 9:55 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
I still think that ExecVacuum() should pass a list of VacuumRelation
objects to vacuum(), and get_rel_oids() should take in input a list,
and return a completed lists. This way the decision-making of doing
everything in the same transaction should happens once in vacuum().
And actually, if several relations are defined with VACUUM, your patch
would not use one transaction per table as use_own_xacts would be set
to false. I think that Tom meant that relations whose processed has
finished have to be committed immediately. Per your patch, the commit
happens once all relations are committed.
Sorry, I must have misunderstood. I've attached an updated patch that
looks more like what you described. I also cleaned up the test cases
a bit.
IIUC the only time use_own_xacts will be false is when we are only
doing ANALYZE and at least one of the following is true:
1. We are in a transaction block.
2. We are processing only one relation.
From the code, it appears that vacuum_rel() always starts and commits a
new transaction for each relation:
* vacuum_rel expects to be entered with no transaction active; it will
* start and commit its own transaction. But we are called by an SQL
So, by ensuring that get_rel_oids() returns a list whenever multiple
tables are specified, we are making sure that commands like
ANALYZE table1, table2, table3;
create transactions for each processed relation (as long as they are
not inside a transaction block). I suppose the alternative would be
to call vacuum() for each relation and to remove the restriction that
we must be processing more than one relation for use_own_xacts to be
true.
Am I understanding this correctly?
Nathan
Attachments:
vacuum_multiple_tables_v20.patchapplication/octet-stream; name=vacuum_multiple_tables_v20.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 1248b2e..5fd146f 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
@@ -152,7 +154,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..6ba83b0 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +47,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +69,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +92,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +120,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +128,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +143,15 @@ 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)
{
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
- List *relations;
static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +194,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +247,21 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
- * Build list of relations to process, unless caller gave us one. (If we
- * build one, we put it in vac_context for safekeeping.)
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
*/
- relations = get_rel_oids(relid, relation);
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Fill in the list of relations with their corresponding OIDs.
+ *
+ * If a listed relation is a partitioned table, an extra entry will be
+ * added to the list for each partition. The list is kept in
+ * vac_context for safekeeping.
+ */
+ get_rel_oids(&relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +329,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +350,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +405,106 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Fill in the list of relations with their corresponding OIDs.
*
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * If a listed relation is a partitioned table, an extra entry will be added to
+ * the list for each partition. The list is kept in vac_context so that it will
+ * survive across our per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +522,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +540,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +555,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
}
/*
@@ -1232,6 +1314,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1309,6 +1392,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14..d32c4e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b91..1d0d779 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818..b3fe50e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index b745d89..b6e30da 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3069,20 +3070,25 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
+ MemoryContext old_cxt;
- /* Set up command parameters --- use local variables instead of palloc */
- MemSet(&rangevar, 0, sizeof(rangevar));
-
- rangevar.schemaname = tab->at_nspname;
- rangevar.relname = tab->at_relname;
- rangevar.location = -1;
+ /*
+ * Create the relation list in a long-lived memory context so that it
+ * survives transaction boundaries.
+ */
+ old_cxt = MemoryContextSwitchTo(AutovacMemCxt);
+ rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
+ MemoryContextSwitchTo(old_cxt);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69..9fdba9e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* table to process (required for error messaging) */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index a16f26d..43c23fc 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -95,4 +93,26 @@ VACUUM ANALYZE vacparted(a,b,a);
ERROR: column "a" of relation "vacparted" is specified twice
ANALYZE vacparted(a,b,b);
ERROR: column "b" of relation "vacparted" is specified twice
+-- 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 vactst, vactst, vactst, vactst;
+VACUUM FULL vactst, vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vacparted".
+ANALYZE vactst, vacparted, 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;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 96a848c..acfe44e 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -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 vactst, vactst, vactst, vactst;
+VACUUM FULL vactst, vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted, 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;
On 9/22/17, 10:56 AM, "Bossart, Nathan" <bossartn@amazon.com> wrote:
Sorry, I must have misunderstood. I've attached an updated patch that
looks more like what you described. I also cleaned up the test cases
a bit.
Here is a version of the patch without the switch to AutovacMemCxt in
autovacuum_do_vac_analyze(), which should no longer be necessary after
335f3d04.
Nathan
Attachments:
vacuum_multiple_tables_v21.patchapplication/octet-stream; name=vacuum_multiple_tables_v21.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 1248b2e..5fd146f 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
@@ -152,7 +154,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..6ba83b0 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +47,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +69,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +92,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +120,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +128,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +143,15 @@ 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)
{
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
- List *relations;
static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +194,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +247,21 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
- * Build list of relations to process, unless caller gave us one. (If we
- * build one, we put it in vac_context for safekeeping.)
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
*/
- relations = get_rel_oids(relid, relation);
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Fill in the list of relations with their corresponding OIDs.
+ *
+ * If a listed relation is a partitioned table, an extra entry will be
+ * added to the list for each partition. The list is kept in
+ * vac_context for safekeeping.
+ */
+ get_rel_oids(&relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +329,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +350,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +405,106 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Fill in the list of relations with their corresponding OIDs.
*
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * If a listed relation is a partitioned table, an extra entry will be added to
+ * the list for each partition. The list is kept in vac_context so that it will
+ * survive across our per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +522,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +540,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +555,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
}
/*
@@ -1232,6 +1314,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1309,6 +1392,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14..d32c4e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b91..1d0d779 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818..b3fe50e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index db6d91f..2797960 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3081,20 +3082,18 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
- /* 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 = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69..9fdba9e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* table to process (required for error messaging) */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index a16f26d..43c23fc 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -95,4 +93,26 @@ VACUUM ANALYZE vacparted(a,b,a);
ERROR: column "a" of relation "vacparted" is specified twice
ANALYZE vacparted(a,b,b);
ERROR: column "b" of relation "vacparted" is specified twice
+-- 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 vactst, vactst, vactst, vactst;
+VACUUM FULL vactst, vacparted (a, b), vaccluster (i);
+ERROR: ANALYZE option must be specified when a column list is provided
+HINT: A column list was specified for relation "vacparted".
+ANALYZE vactst, vacparted, 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;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 96a848c..acfe44e 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -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 vactst, vactst, vactst, vactst;
+VACUUM FULL vactst, vacparted (a, b), vaccluster (i);
+ANALYZE vactst, vacparted, 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;
On Sat, Sep 23, 2017 at 12:56 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 9/21/17, 9:55 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
I still think that ExecVacuum() should pass a list of VacuumRelation
objects to vacuum(), and get_rel_oids() should take in input a list,
and return a completed lists. This way the decision-making of doing
everything in the same transaction should happens once in vacuum().
And actually, if several relations are defined with VACUUM, your patch
would not use one transaction per table as use_own_xacts would be set
to false. I think that Tom meant that relations whose processed has
finished have to be committed immediately. Per your patch, the commit
happens once all relations are committed.Sorry, I must have misunderstood. I've attached an updated patch that
looks more like what you described. I also cleaned up the test cases
a bit.IIUC the only time use_own_xacts will be false is when we are only
doing ANALYZE and at least one of the following is true:1. We are in a transaction block.
2. We are processing only one relation.
Yes.
From the code, it appears that vacuum_rel() always starts and commits a
new transaction for each relation:* vacuum_rel expects to be entered with no transaction active; it will
* start and commit its own transaction. But we are called by an SQL
Yes.
So, by ensuring that get_rel_oids() returns a list whenever multiple
tables are specified, we are making sure that commands likeANALYZE table1, table2, table3;
create transactions for each processed relation (as long as they are
not inside a transaction block).
Yes.
I suppose the alternative would be
to call vacuum() for each relation and to remove the restriction that
we must be processing more than one relation for use_own_xacts to be
true.
The main point of my comment is that like ExecVacuum(), vacuum()
should be a high-level function where is decided if multiple
transactions should be run or not. By calling vacuum() multiple times
you break this promise. vacuum_rel should be the one working with
individual transactions.
Here is the diff between v19 and v21 that matters here:
/* Now go through the common routine */
- if (vacstmt->rels == NIL)
- vacuum(vacstmt->options, NULL, ¶ms, NULL, isTopLevel);
- else
- {
- ListCell *lc;
- foreach(lc, vacstmt->rels)
- vacuum(vacstmt->options, lfirst_node(VacuumRelation, lc),
- ¶ms, NULL, isTopLevel);
- }
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
If you do so, for an ANALYZE with multiple relations you would finish
by using the same transaction for all relations. I think that we had
better be consistent with VACUUM when not using an outer transaction
so as tables are analyzed and committed one by one. This does not
happen here: a unique transaction is used when using a list of
non-partitioned tables.
On Sun, Sep 24, 2017 at 4:37 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Here is a version of the patch without the switch to AutovacMemCxt in
autovacuum_do_vac_analyze(), which should no longer be necessary after
335f3d04.
Thanks for the new version.
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
I like the use of WARNING here, but we could use as well a LOG to be
consistent when a lock obtention is skipped.
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
We will likely have to wait that the matters discussed in
https://www.postgresql.org/message-id/25023.1506107590@sss.pgh.pa.us
are settled.
+VACUUM FULL vactst, vactst, vactst, vactst;
This is actually a waste of cycles.
I think I don't have much other comments about this patch.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/25/17, 12:42 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+ if (!IsAutoVacuumWorkerProcess()) + ereport(WARNING, + (errmsg("skipping \"%s\" --- relation no longer exists", + relation->relname))); I like the use of WARNING here, but we could use as well a LOG to be consistent when a lock obtention is skipped.
It looks like the LOG statement is only emitted for autovacuum, so maybe
we should keep this at WARNING for consistency with the permission checks
below it.
+ * going to commit this transaction and begin a new one between now + * and then. + */ + relid = RangeVarGetRelid(relinfo->relation, NoLock, false); We will likely have to wait that the matters discussed in https://www.postgresql.org/message-id/25023.1506107590@sss.pgh.pa.us are settled.
Makes sense.
+VACUUM FULL vactst, vactst, vactst, vactst;
This is actually a waste of cycles.
I'll clean this up in v22.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Here's a v22. Beyond a rebase, the only real difference is some cleanup
in the test cases.
On 9/26/17, 1:38 PM, "Bossart, Nathan" <bossartn@amazon.com> wrote:
On 9/25/17, 12:42 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+ if (!IsAutoVacuumWorkerProcess()) + ereport(WARNING, + (errmsg("skipping \"%s\" --- relation no longer exists", + relation->relname))); I like the use of WARNING here, but we could use as well a LOG to be consistent when a lock obtention is skipped.It looks like the LOG statement is only emitted for autovacuum, so maybe
we should keep this at WARNING for consistency with the permission checks
below it.
I've left this as-is for now. I considered emitting this statement as a
LOG for autovacuum, but I'm not sure there is terribly much value in
having autovacuum explain that it is skipping a relation because it was
concurrently dropped. Perhaps this is something we should emit at a
DEBUG level. What do you think?
Nathan
Attachments:
vacuum_multiple_tables_v22.patchapplication/octet-stream; name=vacuum_multiple_tables_v22.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 283845c..9b31800 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
@@ -152,7 +154,19 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
relation->relname)));
}
if (!onerel)
+ {
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually analyzed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..6ba83b0 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +47,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +69,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +92,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +120,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +128,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +143,15 @@ 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)
{
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
- List *relations;
static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +194,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +247,21 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
- * Build list of relations to process, unless caller gave us one. (If we
- * build one, we put it in vac_context for safekeeping.)
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
*/
- relations = get_rel_oids(relid, relation);
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Fill in the list of relations with their corresponding OIDs.
+ *
+ * If a listed relation is a partitioned table, an extra entry will be
+ * added to the list for each partition. The list is kept in
+ * vac_context for safekeeping.
+ */
+ get_rel_oids(&relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +329,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +350,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +405,106 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Fill in the list of relations with their corresponding OIDs.
*
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * If a listed relation is a partitioned table, an extra entry will be added to
+ * the list for each partition. The list is kept in vac_context so that it will
+ * survive across our per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +522,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +540,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +555,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
}
/*
@@ -1232,6 +1314,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1309,6 +1392,16 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
if (!onerel)
{
+ /*
+ * If one of the relations specified by the user has disappeared
+ * since we last looked it up, let them know so that they do not
+ * think it was actually vacuumed.
+ */
+ if (!IsAutoVacuumWorkerProcess())
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14..d32c4e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b91..1d0d779 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818..b3fe50e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index db6d91f..2797960 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3081,20 +3082,18 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
- /* 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 = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69..9fdba9e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* table to process (required for error messaging) */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index ced53ca..21778ee 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -95,4 +93,26 @@ 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
+HINT: A column list was specified for relation "vacparted".
+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;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 96a848c..92eaca2 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -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;
On Thu, Sep 28, 2017 at 1:20 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 9/26/17, 1:38 PM, "Bossart, Nathan" <bossartn@amazon.com> wrote:
On 9/25/17, 12:42 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+ if (!IsAutoVacuumWorkerProcess()) + ereport(WARNING, + (errmsg("skipping \"%s\" --- relation no longer exists", + relation->relname))); I like the use of WARNING here, but we could use as well a LOG to be consistent when a lock obtention is skipped.It looks like the LOG statement is only emitted for autovacuum, so maybe
we should keep this at WARNING for consistency with the permission checks
below it.I've left this as-is for now. I considered emitting this statement as a
LOG for autovacuum, but I'm not sure there is terribly much value in
having autovacuum explain that it is skipping a relation because it was
concurrently dropped. Perhaps this is something we should emit at a
DEBUG level. What do you think?
DEBUG would be fine as well for me. Now that your patch provides a
RangeVar consistently for all code paths, the message could show up
unconditionally.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/28/17, 12:20 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
On Thu, Sep 28, 2017 at 1:20 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 9/26/17, 1:38 PM, "Bossart, Nathan" <bossartn@amazon.com> wrote:
On 9/25/17, 12:42 AM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
+ if (!IsAutoVacuumWorkerProcess()) + ereport(WARNING, + (errmsg("skipping \"%s\" --- relation no longer exists", + relation->relname))); I like the use of WARNING here, but we could use as well a LOG to be consistent when a lock obtention is skipped.It looks like the LOG statement is only emitted for autovacuum, so maybe
we should keep this at WARNING for consistency with the permission checks
below it.I've left this as-is for now. I considered emitting this statement as a
LOG for autovacuum, but I'm not sure there is terribly much value in
having autovacuum explain that it is skipping a relation because it was
concurrently dropped. Perhaps this is something we should emit at a
DEBUG level. What do you think?DEBUG would be fine as well for me. Now that your patch provides a
RangeVar consistently for all code paths, the message could show up
unconditionally.
Alright, I've added logging for autovacuum in v23. I ended up needing to
do a little restructuring to handle the case when the relation was skipped
because the lock could not be obtained. While doing so, I became
convinced that LOG was probably the right level for autovacuum logs.
Nathan
Attachments:
vacuum_multiple_tables_v23.patchapplication/octet-stream; name=vacuum_multiple_tables_v23.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 283845c..d391d03 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -116,6 +116,9 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
int elevel;
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ bool rel_lock;
+
+ Assert(relation != NULL);
/* Select logging level */
if (options & VACOPT_VERBOSE)
@@ -138,6 +141,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* matter if we ever try to accumulate stats on dead tuples.) If the rel
* has been dropped since we last saw it, we don't need to process it.
*/
+ rel_lock = true;
if (!(options & VACOPT_NOWAIT))
onerel = try_relation_open(relid, ShareUpdateExclusiveLock);
else if (ConditionalLockRelationOid(relid, ShareUpdateExclusiveLock))
@@ -145,14 +149,40 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
else
{
onerel = NULL;
- if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
- ereport(LOG,
- (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
- errmsg("skipping analyze of \"%s\" --- lock not available",
- relation->relname)));
+ rel_lock = false;
}
+
if (!onerel)
+ {
+ /*
+ * For autovacuum, only emit a LOG if autovacuum_log_min_duration
+ * is not disabled.
+ *
+ * For manual ANALYZE, emit a WARNING.
+ */
+ if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
+ elevel = LOG;
+ else if (!IsAutoVacuumWorkerProcess())
+ elevel = WARNING;
+ else
+ elevel = 0;
+
+ if (elevel != 0)
+ {
+ if (!rel_lock)
+ ereport(elevel,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("skipping analyze of \"%s\" --- lock not available",
+ relation->relname)));
+ else
+ ereport(elevel,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+ }
+
return;
+ }
/*
* Check permissions --- this should match vacuum's check!
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..cb50881 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +47,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +69,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +92,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +120,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +128,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +143,15 @@ 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)
{
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
- List *relations;
static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +194,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +247,21 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
- * Build list of relations to process, unless caller gave us one. (If we
- * build one, we put it in vac_context for safekeeping.)
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
*/
- relations = get_rel_oids(relid, relation);
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Fill in the list of relations with their corresponding OIDs.
+ *
+ * If a listed relation is a partitioned table, an extra entry will be
+ * added to the list for each partition. The list is kept in
+ * vac_context for safekeeping.
+ */
+ get_rel_oids(&relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +329,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +350,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +405,106 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Fill in the list of relations with their corresponding OIDs.
*
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * If a listed relation is a partitioned table, an extra entry will be added to
+ * the list for each partition. The list is kept in vac_context so that it will
+ * survive across our per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
- {
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
+ if (*vacrels != NIL)
{
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +522,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +540,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +555,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
}
/*
@@ -1230,8 +1312,10 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
Oid save_userid;
int save_sec_context;
int save_nestlevel;
+ bool rel_lock;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1293,6 +1377,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* If we've been asked not to wait for the relation lock, acquire it first
* in non-blocking mode, before calling try_relation_open().
*/
+ rel_lock = true;
if (!(options & VACOPT_NOWAIT))
onerel = try_relation_open(relid, lmode);
else if (ConditionalLockRelationOid(relid, lmode))
@@ -1300,15 +1385,40 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
else
{
onerel = NULL;
- if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
- ereport(LOG,
- (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
- errmsg("skipping vacuum of \"%s\" --- lock not available",
- relation->relname)));
+ rel_lock = false;
}
if (!onerel)
{
+ int elevel;
+
+ /*
+ * For autovacuum, only emit a LOG if autovacuum_log_min_duration
+ * is not disabled.
+ *
+ * For manual VACUUM, emit a WARNING.
+ */
+ if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
+ elevel = LOG;
+ else if (!IsAutoVacuumWorkerProcess())
+ elevel = WARNING;
+ else
+ elevel = 0;
+
+ if (elevel != 0)
+ {
+ if (!rel_lock)
+ ereport(elevel,
+ (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+ errmsg("skipping vacuum of \"%s\" --- lock not available",
+ relation->relname)));
+ else
+ ereport(elevel,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("skipping \"%s\" --- relation no longer exists",
+ relation->relname)));
+ }
+
PopActiveSnapshot();
CommitTransactionCommand();
return false;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14..d32c4e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b91..1d0d779 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818..b3fe50e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index db6d91f..2797960 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3081,20 +3082,18 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
- /* 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 = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69..9fdba9e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* table to process (required for error messaging) */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index ced53ca..21778ee 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -95,4 +93,26 @@ 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
+HINT: A column list was specified for relation "vacparted".
+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;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 96a848c..92eaca2 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -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;
On Fri, Sep 29, 2017 at 2:44 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Alright, I've added logging for autovacuum in v23. I ended up needing to
do a little restructuring to handle the case when the relation was skipped
because the lock could not be obtained. While doing so, I became
convinced that LOG was probably the right level for autovacuum logs.
+ if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
+ elevel = LOG;
+ else if (!IsAutoVacuumWorkerProcess())
+ elevel = WARNING;
+ else
+ elevel = 0;
OK, of course let's not change the existing log levels. This could be
always tuned later on depending on feedback from others. I can see
that guc.c also uses elevel == 0 for some logic, so we could rely on
that as you do.
@@ -116,6 +116,9 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
int elevel;
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ bool rel_lock;
+
+ Assert(relation != NULL);
I can see that this is new in your patch. Definitely adapted.
In short, I am switching it back to "ready for committer". We may want
the locking issues when building the relation list to be settled
first.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael Paquier <michael.paquier@gmail.com> writes:
On Fri, Sep 29, 2017 at 2:44 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Alright, I've added logging for autovacuum in v23. I ended up needing to
do a little restructuring to handle the case when the relation was skipped
because the lock could not be obtained. While doing so, I became
convinced that LOG was probably the right level for autovacuum logs.
OK, of course let's not change the existing log levels. This could be
always tuned later on depending on feedback from others. I can see
that guc.c also uses elevel == 0 for some logic, so we could rely on
that as you do.
FWIW, I don't think this patch should be mucking with logging behavior
at all; that's not within its headline charter, and I doubt many people
are paying attention. I propose to commit it without that. If you feel
hot about changing the logging behavior, you can resubmit that as a new
patch in a new thread where it will get some visibility and debate on
its own merits.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 29, 2017 at 10:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
On Fri, Sep 29, 2017 at 2:44 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Alright, I've added logging for autovacuum in v23. I ended up needing to
do a little restructuring to handle the case when the relation was skipped
because the lock could not be obtained. While doing so, I became
convinced that LOG was probably the right level for autovacuum logs.OK, of course let's not change the existing log levels. This could be
always tuned later on depending on feedback from others. I can see
that guc.c also uses elevel == 0 for some logic, so we could rely on
that as you do.FWIW, I don't think this patch should be mucking with logging behavior
at all; that's not within its headline charter, and I doubt many people
are paying attention. I propose to commit it without that. If you feel
hot about changing the logging behavior, you can resubmit that as a new
patch in a new thread where it will get some visibility and debate on
its own merits.
Okay. I am fine with that as well.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/28/17, 8:46 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
On Fri, Sep 29, 2017 at 10:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
On Fri, Sep 29, 2017 at 2:44 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Alright, I've added logging for autovacuum in v23. I ended up needing to
do a little restructuring to handle the case when the relation was skipped
because the lock could not be obtained. While doing so, I became
convinced that LOG was probably the right level for autovacuum logs.OK, of course let's not change the existing log levels. This could be
always tuned later on depending on feedback from others. I can see
that guc.c also uses elevel == 0 for some logic, so we could rely on
that as you do.FWIW, I don't think this patch should be mucking with logging behavior
at all; that's not within its headline charter, and I doubt many people
are paying attention. I propose to commit it without that. If you feel
hot about changing the logging behavior, you can resubmit that as a new
patch in a new thread where it will get some visibility and debate on
its own merits.Okay. I am fine with that as well.
Sure, that seems reasonable to me.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/28/17, 10:05 PM, "Bossart, Nathan" <bossartn@amazon.com> wrote:
On 9/28/17, 8:46 PM, "Michael Paquier" <michael.paquier@gmail.com> wrote:
On Fri, Sep 29, 2017 at 10:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
On Fri, Sep 29, 2017 at 2:44 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
Alright, I've added logging for autovacuum in v23. I ended up needing to
do a little restructuring to handle the case when the relation was skipped
because the lock could not be obtained. While doing so, I became
convinced that LOG was probably the right level for autovacuum logs.OK, of course let's not change the existing log levels. This could be
always tuned later on depending on feedback from others. I can see
that guc.c also uses elevel == 0 for some logic, so we could rely on
that as you do.FWIW, I don't think this patch should be mucking with logging behavior
at all; that's not within its headline charter, and I doubt many people
are paying attention. I propose to commit it without that. If you feel
hot about changing the logging behavior, you can resubmit that as a new
patch in a new thread where it will get some visibility and debate on
its own merits.Okay. I am fine with that as well.
Sure, that seems reasonable to me.
Here's a version without the logging changes in vacuum_rel() and
analyze_rel(). I’ll look into submitting those in the next commitfest.
Nathan
Attachments:
vacuum_multiple_tables_v24.patchapplication/octet-stream; name=vacuum_multiple_tables_v24.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee10..c69a349 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d..3a0afa3 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 283845c..a9ae529 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -117,6 +117,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
AcquireSampleRowsFunc acquirefunc = NULL;
BlockNumber relpages = 0;
+ Assert(relation != NULL);
+
/* Select logging level */
if (options & VACOPT_VERBOSE)
elevel = INFO;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index faa1812..a159d79 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +47,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +69,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +92,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +120,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,15 +128,12 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* options is a bitmask of VacuumOption flags, indicating what to do.
*
- * relid, if not InvalidOid, indicate the relation to process; otherwise,
- * the RangeVar is used. (The latter must always be passed, because it's
- * used for error messages.)
+ * relations is a list of VacuumRelation to process. If it is NIL, all
+ * relations in the database are processed.
*
* params contains a set of parameters that can be used to customize the
* behavior.
*
- * va_cols is a list of columns to analyze, or NIL to process them all.
- *
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
*
@@ -146,14 +143,15 @@ 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)
{
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
- List *relations;
static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -196,6 +194,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -226,10 +247,21 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_strategy = bstrategy;
/*
- * Build list of relations to process, unless caller gave us one. (If we
- * build one, we put it in vac_context for safekeeping.)
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
*/
- relations = get_rel_oids(relid, relation);
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Fill in the list of relations with their corresponding OIDs.
+ *
+ * If a listed relation is a partitioned table, an extra entry will be
+ * added to the list for each partition. The list is kept in
+ * vac_context for safekeeping.
+ */
+ get_rel_oids(&relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -297,11 +329,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -318,8 +350,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -373,67 +405,106 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Fill in the list of relations with their corresponding OIDs.
*
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * If a listed relation is a partitioned table, an extra entry will be added to
+ * the list for each partition. The list is kept in vac_context so that it will
+ * survive across our per-relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s) */
+ ListCell *lc;
- /*
- * Since we don't take a lock here, the relation might be gone, or the
- * RangeVar might no longer refer to the OID we look up here. In the
- * former case, VACUUM will do nothing; in the latter case, it will
- * process the OID we looked up here, rather than the new one. Neither
- * is ideal, but there's little practical alternative, since we're
- * going to commit this transaction and begin a new one between now
- * and then.
- */
- relid = RangeVarGetRelid(vacrel, NoLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * Make relation list entries for this guy and its partitions, if any.
- * Note that the list returned by find_all_inheritors() include the
- * passed-in OID at its head. Also note that we did not request a
- * lock to be taken to match what would be done otherwise.
- */
- oldcontext = MemoryContextSwitchTo(vac_context);
- if (include_parts)
- oid_list = list_concat(oid_list,
- find_all_inheritors(relid, NoLock, NULL));
- else
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
+
+ /*
+ * Since we don't take a lock here, the relation might be gone, or the
+ * RangeVar might no longer refer to the OID we look up here. In the
+ * former case, VACUUM will do nothing; in the latter case, it will
+ * process the OID we looked up here, rather than the new one. Neither
+ * is ideal, but there's little practical alternative, since we're
+ * going to commit this transaction and begin a new one between now
+ * and then.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, NoLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ ReleaseSysCache(tuple);
+
+ /*
+ * Make relation list entries for this guy and its partitions, if any.
+ * Note that the list returned by find_all_inheritors() include the
+ * passed-in OID at its head. Also note that we did not request a
+ * lock to be taken to match what would be done otherwise.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+ relname = get_rel_name(part_oid);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
}
else
{
@@ -451,7 +522,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -465,7 +540,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -473,7 +555,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
}
/*
@@ -1232,6 +1314,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
int save_nestlevel;
Assert(params != NULL);
+ Assert(relation != NULL);
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14..d32c4e6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3767,8 +3767,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -5216,6 +5227,9 @@ copyObjectImpl(const void *from)
case T_VacuumStmt:
retval = _copyVacuumStmt(from);
break;
+ case T_VacuumRelation:
+ retval = _copyVacuumRelation(from);
+ break;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8b56b91..1d0d779 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1664,8 +1664,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -3362,6 +3371,9 @@ equal(const void *a, const void *b)
case T_VacuumStmt:
retval = _equalVacuumStmt(a, b);
break;
+ case T_VacuumRelation:
+ retval = _equalVacuumRelation(a, b);
+ break;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039..60d6add 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818..b3fe50e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index db6d91f..2797960 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3081,20 +3082,18 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
- /* 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 = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a903511..7a7b793 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1..12d78ff 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3..ffeeb49 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69..9fdba9e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* table to process (required for error messaging) */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index ced53ca..21778ee 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -95,4 +93,26 @@ 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
+HINT: A column list was specified for relation "vacparted".
+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;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 96a848c..92eaca2 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -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;
On 9/29/17, 9:33 AM, "Bossart, Nathan" <bossartn@amazon.com> wrote:
Here's a version without the logging changes in vacuum_rel() and
analyze_rel(). I’ll look into submitting those in the next commitfest.
Since get_rel_oids() was altered in 19de0ab2, here is a new version of
the patch.
Nathan
Attachments:
vacuum_multiple_tables_v25.patchapplication/octet-stream; name=vacuum_multiple_tables_v25.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 45dee101df..c69a3490bf 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 421c18d117..3a0afa3b74 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -21,9 +21,21 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> ]
-VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_name</replaceable> [ (<replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] ]
+VACUUM [ ( <replaceable class="PARAMETER">option</replaceable> [, ...] ) ] [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [, ...] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table_and_columns</replaceable> [, ...] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be one of:</phrase>
+
+ FULL
+ FREEZE
+ VERBOSE
+ ANALYZE
+ DISABLE_PAGE_SKIPPING
+
+<phrase>and <replaceable class="PARAMETER">table_and_columns</replaceable> is:</phrase>
+
+ <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
</synopsis>
</refsynopsisdiv>
@@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
The name of a specific column to analyze. Defaults to all columns.
- If a column list is specified, <literal>ANALYZE</> is implied.
+ If a column list is specified, <literal>ANALYZE</> must also be
+ specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d533cef6a6..710b5ccdcd 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -37,6 +37,7 @@
#include "commands/cluster.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
@@ -46,6 +47,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -67,7 +69,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
-static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
+static void get_rel_oids(List **vacrels);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
@@ -90,7 +92,6 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((vacstmt->options & VACOPT_VACUUM) ||
!(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
- Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
Assert(!(vacstmt->options & VACOPT_SKIPTOAST));
/*
@@ -119,8 +120,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
params.log_min_duration = -1;
/* Now go through the common routine */
- vacuum(vacstmt->options, vacstmt->relation, InvalidOid, ¶ms,
- vacstmt->va_cols, NULL, isTopLevel);
+ vacuum(vacstmt->options, vacstmt->rels, ¶ms, NULL, isTopLevel);
}
/*
@@ -128,17 +128,15 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
*
* 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 is processed;
+ * otherwise, the VacuumRelation's RangeVar indicates what to process. (In
+ * either case, a RangeVar must be provided for error reporting).
*
* 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 +146,15 @@ 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)
{
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
- List *relations;
static bool in_vacuum = false;
+ MemoryContext oldcontext;
+ ListCell *lc;
Assert(params != NULL);
@@ -198,6 +197,29 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/*
+ * Make sure VACOPT_ANALYZE is specified if any column lists are present.
+ */
+ foreach(lc, relations)
+ {
+ VacuumRelation *relation = lfirst_node(VacuumRelation, lc);
+ if (relation->va_cols != NIL && (options & VACOPT_ANALYZE) == 0)
+ {
+ char *rel_name;
+ RangeVar *rangeVar = relation->relation;
+
+ if (rangeVar->schemaname != NULL)
+ rel_name = psprintf("%s.%s", rangeVar->schemaname, rangeVar->relname);
+ else
+ rel_name = rangeVar->relname;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ANALYZE option must be specified when a column list is provided"),
+ errhint("A column list was specified for relation \"%s\".", rel_name)));
+ }
+ }
+
+ /*
* Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself.
*/
@@ -228,10 +250,21 @@ 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.
+ * Move our relation list to our special memory context so that we do
+ * not lose it among our per-relation transactions.
*/
- relations = get_rel_oids(relid, relation);
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ relations = copyObject(relations);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Fill in the list of relations with their corresponding OIDs.
+ *
+ * If a listed relation is a partitioned table, an extra entry will be
+ * added to the list for each partition. The list is kept in
+ * vac_context for safekeeping.
+ */
+ get_rel_oids(&relations);
/*
* Decide whether we need to start/commit our own transactions.
@@ -299,11 +332,11 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
*/
foreach(cur, relations)
{
- Oid relid = lfirst_oid(cur);
+ VacuumRelation *relinfo = lfirst_node(VacuumRelation, cur);
if (options & VACOPT_VACUUM)
{
- if (!vacuum_rel(relid, relation, options, params))
+ if (!vacuum_rel(relinfo->oid, relinfo->relation, options, params))
continue;
}
@@ -320,8 +353,8 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, relation, options, params,
- va_cols, in_outer_xact, vac_strategy);
+ analyze_rel(relinfo->oid, relinfo->relation, options, params,
+ relinfo->va_cols, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
@@ -375,77 +408,132 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
}
/*
- * Build a list of Oids for each relation to be processed
+ * Fill in the list of relations with their corresponding OIDs.
+ *
+ * If a listed relation does not have an OID supplied and is a partitioned
+ * table, an extra entry will be added to the list for each partition.
+ * Presently, only autovacuum supplies OIDs when calling vacuum(), and
+ * it does not operate on partitioned tables.
*
- * The list is built in vac_context so that it will survive across our
- * per-relation transactions.
+ * The list is kept in vac_context so that it will survive across our per-
+ * relation transactions.
*/
-static List *
-get_rel_oids(Oid relid, const RangeVar *vacrel)
+static void
+get_rel_oids(List **vacrels)
{
- List *oid_list = NIL;
+ List *vacrels_tmp = NIL;
+ VacuumRelation *relinfo;
MemoryContext oldcontext;
- /* OID supplied by VACUUM's caller? */
- if (OidIsValid(relid))
+ if (*vacrels != NIL)
{
- oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, relid);
- MemoryContextSwitchTo(oldcontext);
- }
- else if (vacrel)
- {
- /* Process a specific relation, and possibly partitions thereof */
- Oid relid;
- HeapTuple tuple;
- Form_pg_class classForm;
- bool include_parts;
+ /* Process specific relation(s), and possibly paritions thereof */
+ ListCell *lc;
- /*
- * We transiently take AccessShareLock to protect the syscache lookup
- * below, as well as find_all_inheritors's expectation that the caller
- * holds some lock on the starting relation.
- */
- relid = RangeVarGetRelid(vacrel, AccessShareLock, false);
+ foreach(lc, *vacrels)
+ {
+ Oid relid;
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ bool include_parts;
- /*
- * To check whether the relation is a partitioned table, fetch its
- * syscache entry.
- */
- tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", relid);
- classForm = (Form_pg_class) GETSTRUCT(tuple);
- include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
- ReleaseSysCache(tuple);
+ relinfo = lfirst_node(VacuumRelation, lc);
- /*
- * 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.
- */
- 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);
+ /* OIDs supplied by VACUUM's caller? */
+ if (OidIsValid(relinfo->oid))
+ {
+ oldcontext = MemoryContextSwitchTo(vac_context);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ MemoryContextSwitchTo(oldcontext);
+ continue;
+ }
- /*
- * Release lock again. This means that by the time we actually try to
- * process the table, it might be gone or renamed. In the former case
- * we'll silently ignore it; in the latter case we'll process it
- * anyway, but we must beware that the RangeVar doesn't necessarily
- * identify it anymore. This isn't ideal, perhaps, but there's little
- * practical alternative, since we're typically going to commit this
- * transaction and begin a new one between now and then. Moreover,
- * holding locks on multiple relations would create significant risk
- * of deadlock.
- */
- UnlockRelationOid(relid, AccessShareLock);
+ /*
+ * We transiently take AccessShareLock to protect the syscache lookup
+ * below, as well as find_all_inheritors's expectation that the caller
+ * holds some lock on the starting relation.
+ */
+ relid = RangeVarGetRelid(relinfo->relation, AccessShareLock, false);
+
+ /*
+ * To check whether the relation is a partitioned table, fetch its
+ * syscache entry.
+ */
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
+ 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.
+ */
+ oldcontext = MemoryContextSwitchTo(vac_context);
+
+ if (include_parts)
+ {
+ List *partition_oids = find_all_inheritors(relid, NoLock, NULL);
+ ListCell *part_lc;
+ foreach(part_lc, partition_oids)
+ {
+ RangeVar *rangevar;
+ VacuumRelation *tmp;
+ char *schemaname = NULL;
+ char *relname = NULL;
+ Oid part_oid;
+ Oid namespace_oid;
+
+ part_oid = lfirst_oid(part_lc);
+
+ namespace_oid = get_rel_namespace(part_oid);
+ if (OidIsValid(namespace_oid))
+ schemaname = get_namespace_name(namespace_oid);
+
+ relname = get_rel_name(part_oid);
+
+ /*
+ * Concurrent changes may have caused the schema or
+ * relation names we looked up here to be incorrect, but
+ * that is ultimately a low risk, as these values are
+ * only used for error reporting and may be changed
+ * further before processing, anyway. However, if the
+ * relation no longer exists, we can skip adding it to
+ * the list.
+ */
+ if (relname == NULL)
+ continue;
+
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ tmp = makeVacuumRelation(rangevar, relinfo->va_cols, part_oid);
+ vacrels_tmp = lappend(vacrels_tmp, tmp);
+ }
+ }
+ else
+ {
+ relinfo->oid = relid;
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Release lock again. This means that by the time we actually try to
+ * process the table, it might be gone or renamed. In the former case
+ * we'll silently ignore it; in the latter case we'll process it
+ * anyway, but we must beware that the RangeVar doesn't necessarily
+ * identify it anymore. This isn't ideal, perhaps, but there's little
+ * practical alternative, since we're typically going to commit this
+ * transaction and begin a new one between now and then. Moreover,
+ * holding locks on multiple relations would create significant risk
+ * of deadlock.
+ */
+ UnlockRelationOid(relid, AccessShareLock);
+ }
}
else
{
@@ -463,7 +551,11 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Oid rel_oid;
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ char *schemaname;
+ char *relname;
+ RangeVar *rangevar;
/*
* We include partitioned tables here; depending on which
@@ -477,7 +569,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
/* Make a relation list entry for this rel */
oldcontext = MemoryContextSwitchTo(vac_context);
- oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
+
+ rel_oid = HeapTupleGetOid(tuple);
+ schemaname = get_namespace_name(classForm->relnamespace);
+ relname = NameStr(classForm->relname);
+ rangevar = makeRangeVar(schemaname, relname, -1);
+ relinfo = makeVacuumRelation(rangevar, NIL, rel_oid);
+ vacrels_tmp = lappend(vacrels_tmp, relinfo);
+
MemoryContextSwitchTo(oldcontext);
}
@@ -485,7 +584,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
heap_close(pgclass, AccessShareLock);
}
- return oid_list;
+ *vacrels = vacrels_tmp;
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b274af26a4..542a09c51d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3766,8 +3766,19 @@ _copyVacuumStmt(const VacuumStmt *from)
VacuumStmt *newnode = makeNode(VacuumStmt);
COPY_SCALAR_FIELD(options);
+ COPY_NODE_FIELD(rels);
+
+ return newnode;
+}
+
+static VacuumRelation *
+_copyVacuumRelation(const VacuumRelation *from)
+{
+ VacuumRelation *newnode = makeNode(VacuumRelation);
+
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(va_cols);
+ COPY_SCALAR_FIELD(oid);
return newnode;
}
@@ -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;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5c839f4c31..b26d3514c4 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,8 +1663,17 @@ static bool
_equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b)
{
COMPARE_SCALAR_FIELD(options);
+ COMPARE_NODE_FIELD(rels);
+
+ return true;
+}
+
+static bool
+_equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
+{
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(va_cols);
+ COMPARE_SCALAR_FIELD(oid);
return true;
}
@@ -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;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0755039da9..60d6addf0f 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -611,3 +611,18 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
+/*
+ * makeVacuumRelation -
+ * create a VacuumRelation node
+ */
+VacuumRelation *
+makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid)
+{
+ VacuumRelation *v = makeNode(VacuumRelation);
+
+ v->relation = relation;
+ v->va_cols = va_cols;
+ v->oid = oid;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c303818c9b..b3fe50efb6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -366,6 +366,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
+%type <node> vacuum_relation
+
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
@@ -395,7 +397,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
- publication_name_list
+ publication_name_list vacuum_relation_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10157,11 +10159,10 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | VACUUM opt_full opt_freeze opt_verbose qualified_name
+ | VACUUM opt_full opt_freeze opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM;
@@ -10171,8 +10172,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
n->options |= VACOPT_FREEZE;
if ($4)
n->options |= VACOPT_VERBOSE;
- n->relation = $5;
- n->va_cols = NIL;
+ n->rels = $5;
$$ = (Node *)n;
}
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
@@ -10191,18 +10191,14 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *) n;
}
- | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list
+ | VACUUM '(' vacuum_option_list ')' vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_VACUUM | $3;
- n->relation = $5;
- n->va_cols = $6;
- if (n->va_cols != NIL) /* implies analyze */
- n->options |= VACOPT_ANALYZE;
+ n->rels = $5;
$$ = (Node *) n;
}
;
@@ -10236,18 +10232,16 @@ AnalyzeStmt:
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = NULL;
- n->va_cols = NIL;
+ n->rels = NIL;
$$ = (Node *)n;
}
- | analyze_keyword opt_verbose qualified_name opt_name_list
+ | analyze_keyword opt_verbose vacuum_relation_list
{
VacuumStmt *n = makeNode(VacuumStmt);
n->options = VACOPT_ANALYZE;
if ($2)
n->options |= VACOPT_VERBOSE;
- n->relation = $3;
- n->va_cols = $4;
+ n->rels = $3;
$$ = (Node *)n;
}
;
@@ -10275,6 +10269,18 @@ opt_name_list:
| /*EMPTY*/ { $$ = NIL; }
;
+vacuum_relation:
+ qualified_name opt_name_list
+ {
+ $$ = (Node *) makeVacuumRelation($1, $2, InvalidOid);
+ }
+ ;
+
+vacuum_relation_list:
+ vacuum_relation { $$ = list_make1($1); }
+ | vacuum_relation_list ',' vacuum_relation { $$ = lappend($1, $3); }
+ ;
+
/*****************************************************************************
*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index db6d91ffdf..2797960fd4 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -79,6 +79,7 @@
#include "lib/ilist.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
@@ -3081,20 +3082,18 @@ relation_needs_vacanalyze(Oid relid,
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
{
- RangeVar rangevar;
+ RangeVar *rangevar;
+ VacuumRelation *rel;
+ List *rel_list;
- /* 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 = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
+ rel = makeVacuumRelation(rangevar, NIL, tab->at_relid);
+ rel_list = list_make1(rel);
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
- vacuum(tab->at_vacoptions, &rangevar, tab->at_relid, &tab->at_params, NIL,
- bstrategy, true);
+ vacuum(tab->at_vacoptions, rel_list, &tab->at_params, bstrategy, true);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index a9035112e9..7a7b793ddf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -157,8 +157,7 @@ extern int vacuum_multixact_freeze_table_age;
/* in commands/vacuum.c */
extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
-extern void vacuum(int options, RangeVar *relation, Oid relid,
- VacuumParams *params, List *va_cols,
+extern void vacuum(int options, List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 46a79b1817..12d78ff923 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -86,4 +86,6 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, List *va_cols, Oid oid);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 27bd4f3363..ffeeb4919b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -468,6 +468,7 @@ typedef enum NodeTag
T_PartitionBoundSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
+ T_VacuumRelation,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f3e4c69753..9fdba9e981 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3098,12 +3098,23 @@ typedef enum VacuumOption
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
+/*
+ * This is used to keep track of a relation and an optional list of
+ * column names, as may be specified in VACUUM and ANALYZE.
+ */
+typedef struct VacuumRelation
+{
+ NodeTag type;
+ RangeVar *relation; /* table to process (required for error messaging) */
+ List *va_cols; /* list of column names, or NIL for all */
+ Oid oid; /* corresponding OID (filled in by [auto]vacuum.c) */
+} VacuumRelation;
+
typedef struct VacuumStmt
{
- NodeTag type;
- int options; /* OR of VacuumOption flags */
- RangeVar *relation; /* single table to process, or NULL */
- List *va_cols; /* list of column names, or NIL for all */
+ NodeTag type;
+ int options; /* OR of VacuumOption flags */
+ List *rels; /* list of tables/columns to process, or NIL for all */
} VacuumStmt;
/* ----------------------
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index ced53ca9aa..21778ee56e 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -80,8 +80,6 @@ CONTEXT: SQL function "do_analyze" statement 1
SQL function "wrap_do_analyze" statement 1
VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -95,4 +93,26 @@ 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
+HINT: A column list was specified for relation "vacparted".
+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;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 96a848ca95..92eaca2a93 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -62,9 +62,6 @@ VACUUM FULL vactst;
VACUUM (DISABLE_PAGE_SKIPPING) vaccluster;
-DROP TABLE vaccluster;
-DROP TABLE vactst;
-
-- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
@@ -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;
On Mon, Oct 2, 2017 at 1:43 PM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 9/29/17, 9:33 AM, "Bossart, Nathan" <bossartn@amazon.com> wrote:
Here's a version without the logging changes in vacuum_rel() and
analyze_rel(). I’ll look into submitting those in the next commitfest.Since get_rel_oids() was altered in 19de0ab2, here is a new version of
the patch.
Okay, I think that you got that right.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"Bossart, Nathan" <bossartn@amazon.com> writes:
Since get_rel_oids() was altered in 19de0ab2, here is a new version of
the patch.
I thought it would be a good idea to get this done before walking away
from the commitfest and letting all this info get swapped out of my
head. So I've reviewed and pushed this.
I took out most of the infrastructure you'd put in for constructing
RangeVars for tables not explicitly named on the command line. It
was buggy (eg you can't assume a relcache entry will stick around)
and I don't believe it's necessary. I don't think that warnings
should be issued for any tables not explicitly named.
In any case, though, the extent to which we should add more warning
or log output seems like a fit topic for a new thread and a separate
patch. Let's call this one done.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/3/17, 5:59 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:
I thought it would be a good idea to get this done before walking away
from the commitfest and letting all this info get swapped out of my
head. So I've reviewed and pushed this.
Thanks!
I took out most of the infrastructure you'd put in for constructing
RangeVars for tables not explicitly named on the command line. It
was buggy (eg you can't assume a relcache entry will stick around)
and I don't believe it's necessary. I don't think that warnings
should be issued for any tables not explicitly named.In any case, though, the extent to which we should add more warning
or log output seems like a fit topic for a new thread and a separate
patch. Let's call this one done.
I'll look into submitting that to the next commitfest.
Nathan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 4, 2017 at 9:52 AM, Bossart, Nathan <bossartn@amazon.com> wrote:
On 10/3/17, 5:59 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:
I thought it would be a good idea to get this done before walking away
from the commitfest and letting all this info get swapped out of my
head. So I've reviewed and pushed this.Thanks!
+1.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers