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 -ANALYZE [ VERBOSE ] [ table_name [ ( column_name [, ...] ) ] ] +ANALYZE [ VERBOSE ] [ table_and_columns [, ...] ] + +where table_and_columns is: + + table_name [ ( column_name [, ...] ) ] 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 -VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE | DISABLE_PAGE_SKIPPING } [, ...] ) ] [ table_name [ (column_name [, ...] ) ] ] -VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table_name ] -VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table_name [ (column_name [, ...] ) ] ] +VACUUM [ ( option [, ...] ) ] [ table_and_columns [, ...] ] +VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table_name [, ...] ] +VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table_and_columns [, ...] ] + +where option can be one of: + + FULL + FREEZE + VERBOSE + ANALYZE + DISABLE_PAGE_SKIPPING + +and table_and_columns is: + + table_name [ ( column_name [, ...] ) ] @@ -165,7 +177,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ The name of a specific column to analyze. Defaults to all columns. - If a column list is specified, ANALYZE is implied. + If a column list is specified, ANALYZE must also be + specified. 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 import_qualification_type %type import_qualification +%type vacuum_relation + %type 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 group_by_list %type 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;