diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index 3b5d241703da75a9ee232ca6172cc5b020d4a857..07559e38c42eefe787ca04211f5d150c630fcf5e 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -1,5 +1,5 @@ @@ -21,7 +21,7 @@ PostgreSQL documentation -VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE } [, ...] ) ] [ table [ (column [, ...] ) ] ] +VACUUM [ ( { FULL [ INPLACE ] | FREEZE | VERBOSE | ANALYZE } [, ...] ) ] [ table [ (column [, ...] ) ] ] VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table ] VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ] @@ -86,6 +86,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ Selects full vacuum, which can reclaim more space, but takes much longer and exclusively locks the table. + + For user tables, all table data and indexes are rewritten. This + method requires extra disk space in which to write the new data, + and is generally useful when a significant amount of space needs + to be reclaimed from within the table. + + + For system tables, all table data and indexes are modified in + place to reclaim space. This method may require less disk space + for the table data than VACUUM FULL on a + comparable user table, but the indexes will grow which may + counteract that benefit. Additionally, the operation is often + slower than VACUUM FULL on a comparable user + table. + + + If FULL INPLACE is specified, the space is + reclaimed in the same manner as a system table, even if it is a + user table. Specifying INPLACE explicitly is + rarely useful. + diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml index ad6f3a8cf3d29a155c36375d7c2af46003cede24..2d891c56cf69f54bafe1cd607960097f90808af3 100644 --- a/doc/src/sgml/ref/vacuumdb.sgml +++ b/doc/src/sgml/ref/vacuumdb.sgml @@ -1,5 +1,5 @@ @@ -24,6 +24,7 @@ PostgreSQL documentation vacuumdb connection-option --full-f + --inplace-i --freeze-F --verbose-v --analyze-z @@ -37,6 +38,7 @@ PostgreSQL documentation connection-options --all-a --full-f + --inplace-i --freeze-F --verbose-v --analyze-z @@ -129,6 +131,16 @@ PostgreSQL documentation + + + + + + Perform full inplace vacuuming. + + + + diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 48ba57518f41ae955ba59e8ca96720d4bed116d5..1b1053d8a87bbc18fc7d643e2cbcecdae3c68dbf 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.190 2010/01/06 03:04:00 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.191 2010/01/06 05:31:13 itagaki Exp $ * *------------------------------------------------------------------------- */ @@ -61,9 +61,10 @@ typedef struct } RelToCluster; -static void cluster_rel(RelToCluster *rv, bool recheck, bool verbose); -static void rebuild_relation(Relation OldHeap, Oid indexOid); -static TransactionId copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex); +static void rebuild_relation(Relation OldHeap, Oid indexOid, + int freeze_min_age, int freeze_table_age); +static TransactionId copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, + Oid OIDOldIndex, int freeze_min_age, int freeze_table_age); static List *get_tables_to_cluster(MemoryContext cluster_context); @@ -101,7 +102,6 @@ cluster(ClusterStmt *stmt, bool isTopLevel) Oid tableOid, indexOid = InvalidOid; Relation rel; - RelToCluster rvtc; /* Find and lock the table */ rel = heap_openrv(stmt->relation, AccessExclusiveLock); @@ -169,15 +169,11 @@ cluster(ClusterStmt *stmt, bool isTopLevel) stmt->indexname, stmt->relation->relname))); } - /* All other checks are done in cluster_rel() */ - rvtc.tableOid = tableOid; - rvtc.indexOid = indexOid; - /* close relation, keep lock till commit */ heap_close(rel, NoLock); /* Do the job */ - cluster_rel(&rvtc, false, stmt->verbose); + cluster_rel(tableOid, indexOid, false, stmt->verbose, -1, -1); } else { @@ -226,7 +222,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel) StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); - cluster_rel(rvtc, true, stmt->verbose); + cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose, -1, -1); PopActiveSnapshot(); CommitTransactionCommand(); } @@ -252,9 +248,13 @@ cluster(ClusterStmt *stmt, bool isTopLevel) * same way we do for the relation. Since we are effectively bulk-loading * the new table, it's better to create the indexes afterwards than to fill * them incrementally while we load the table. + * + * If indexOid is InvalidOid, the table will be rewritten in physical order + * instead of index order. */ -static void -cluster_rel(RelToCluster *rvtc, bool recheck, bool verbose) +void +cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, + int freeze_min_age, int freeze_table_age) { Relation OldHeap; @@ -267,7 +267,7 @@ cluster_rel(RelToCluster *rvtc, bool recheck, bool verbose) * case, since cluster() already did it.) The index lock is taken inside * check_index_is_clusterable. */ - OldHeap = try_relation_open(rvtc->tableOid, AccessExclusiveLock); + OldHeap = try_relation_open(tableOid, AccessExclusiveLock); /* If the table has gone away, we can skip processing it */ if (!OldHeap) @@ -287,7 +287,7 @@ cluster_rel(RelToCluster *rvtc, bool recheck, bool verbose) Form_pg_index indexForm; /* Check that the user still owns the relation */ - if (!pg_class_ownercheck(rvtc->tableOid, GetUserId())) + if (!pg_class_ownercheck(tableOid, GetUserId())) { relation_close(OldHeap, AccessExclusiveLock); return; @@ -308,53 +308,62 @@ cluster_rel(RelToCluster *rvtc, bool recheck, bool verbose) return; } - /* - * Check that the index still exists - */ - if (!SearchSysCacheExists(RELOID, - ObjectIdGetDatum(rvtc->indexOid), - 0, 0, 0)) + if (OidIsValid(indexOid)) { - relation_close(OldHeap, AccessExclusiveLock); - return; - } + /* + * Check that the index still exists + */ + if (!SearchSysCacheExists(RELOID, + ObjectIdGetDatum(indexOid), + 0, 0, 0)) + { + relation_close(OldHeap, AccessExclusiveLock); + return; + } - /* - * Check that the index is still the one with indisclustered set. - */ - tuple = SearchSysCache(INDEXRELID, - ObjectIdGetDatum(rvtc->indexOid), - 0, 0, 0); - if (!HeapTupleIsValid(tuple)) /* probably can't happen */ - { - relation_close(OldHeap, AccessExclusiveLock); - return; - } - indexForm = (Form_pg_index) GETSTRUCT(tuple); - if (!indexForm->indisclustered) - { + /* + * Check that the index is still the one with indisclustered set. + */ + tuple = SearchSysCache(INDEXRELID, + ObjectIdGetDatum(indexOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) /* probably can't happen */ + { + relation_close(OldHeap, AccessExclusiveLock); + return; + } + indexForm = (Form_pg_index) GETSTRUCT(tuple); + if (!indexForm->indisclustered) + { + ReleaseSysCache(tuple); + relation_close(OldHeap, AccessExclusiveLock); + return; + } ReleaseSysCache(tuple); - relation_close(OldHeap, AccessExclusiveLock); - return; } - ReleaseSysCache(tuple); } - /* Check index is valid to cluster on */ - check_index_is_clusterable(OldHeap, rvtc->indexOid, recheck); + /* Check heap and index are valid to cluster on */ + check_index_is_clusterable(OldHeap, indexOid, recheck); /* rebuild_relation does all the dirty work */ - ereport(verbose ? INFO : DEBUG2, - (errmsg("clustering \"%s.%s\"", - get_namespace_name(RelationGetNamespace(OldHeap)), - RelationGetRelationName(OldHeap)))); - rebuild_relation(OldHeap, rvtc->indexOid); + if (OidIsValid(indexOid)) + ereport(verbose ? INFO : DEBUG2, + (errmsg("clustering \"%s.%s\"", + get_namespace_name(RelationGetNamespace(OldHeap)), + RelationGetRelationName(OldHeap)))); + else + ereport(verbose ? INFO : DEBUG2, + (errmsg("vacuuming \"%s.%s\"", + get_namespace_name(RelationGetNamespace(OldHeap)), + RelationGetRelationName(OldHeap)))); + rebuild_relation(OldHeap, indexOid, freeze_min_age, freeze_table_age); /* NB: rebuild_relation does heap_close() on OldHeap */ } /* - * Verify that the specified index is a legitimate index to cluster on + * Verify that the specified heap and index are valid to cluster on * * Side effect: obtains exclusive lock on the index. The caller should * already have exclusive lock on the table, so the index lock is likely @@ -366,6 +375,38 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck) { Relation OldIndex; + /* + * Disallow clustering system relations. This will definitely NOT work + * for shared relations (we have no way to update pg_class rows in other + * databases), nor for nailed-in-cache relations (the relfilenode values + * for those are hardwired, see relcache.c). It might work for other + * system relations, but I ain't gonna risk it. + */ + if (IsSystemRelation(OldHeap)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"%s\" is a system catalog", + RelationGetRelationName(OldHeap)))); + + /* + * Don't allow cluster on temp tables of other backends ... their local + * buffer manager is not going to cope. + */ + if (RELATION_IS_OTHER_TEMP(OldHeap)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot cluster temporary tables of other sessions"))); + + /* + * Also check for active uses of the relation in the current transaction, + * including open scans and pending AFTER trigger events. + */ + CheckTableNotInUse(OldHeap, "CLUSTER"); + + /* Skip checks for index if not specified. */ + if (!OidIsValid(indexOid)) + return; + OldIndex = index_open(indexOid, AccessExclusiveLock); /* @@ -448,34 +489,6 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck) errmsg("cannot cluster on invalid index \"%s\"", RelationGetRelationName(OldIndex)))); - /* - * Disallow clustering system relations. This will definitely NOT work - * for shared relations (we have no way to update pg_class rows in other - * databases), nor for nailed-in-cache relations (the relfilenode values - * for those are hardwired, see relcache.c). It might work for other - * system relations, but I ain't gonna risk it. - */ - if (IsSystemRelation(OldHeap)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("\"%s\" is a system catalog", - RelationGetRelationName(OldHeap)))); - - /* - * Don't allow cluster on temp tables of other backends ... their local - * buffer manager is not going to cope. - */ - if (RELATION_IS_OTHER_TEMP(OldHeap)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot cluster temporary tables of other sessions"))); - - /* - * Also check for active uses of the relation in the current transaction, - * including open scans and pending AFTER trigger events. - */ - CheckTableNotInUse(OldHeap, "CLUSTER"); - /* Drop relcache refcnt on OldIndex, but keep lock */ index_close(OldIndex, NoLock); } @@ -557,15 +570,16 @@ mark_index_clustered(Relation rel, Oid indexOid) } /* - * rebuild_relation: rebuild an existing relation in index order + * rebuild_relation: rebuild an existing relation in index or physical order * * OldHeap: table to rebuild --- must be opened and exclusive-locked! - * indexOid: index to cluster by + * indexOid: index to cluster by, or InvalidOid to rewrite in physical order. * * NB: this routine closes OldHeap at the right time; caller should not. */ static void -rebuild_relation(Relation OldHeap, Oid indexOid) +rebuild_relation(Relation OldHeap, Oid indexOid, + int freeze_min_age, int freeze_table_age) { Oid tableOid = RelationGetRelid(OldHeap); Oid tableSpace = OldHeap->rd_rel->reltablespace; @@ -576,7 +590,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid) Relation newrel; /* Mark the correct index as clustered */ - mark_index_clustered(OldHeap, indexOid); + if (OidIsValid(indexOid)) + mark_index_clustered(OldHeap, indexOid); /* Close relcache entry, but keep lock until transaction commit */ heap_close(OldHeap, NoLock); @@ -599,7 +614,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid) /* * Copy the heap data into the new table in the desired order. */ - frozenXid = copy_heap_data(OIDNewHeap, tableOid, indexOid); + frozenXid = copy_heap_data(OIDNewHeap, tableOid, indexOid, + freeze_min_age, freeze_table_age); /* To make the new heap's data visible (probably not needed?). */ CommandCounterIncrement(); @@ -758,7 +774,8 @@ make_new_heap(Oid OIDOldHeap, const char *NewName, Oid NewTableSpace) * freeze cutoff point for the tuples. */ static TransactionId -copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) +copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, + int freeze_min_age, int freeze_table_age) { Relation NewHeap, OldHeap, @@ -768,8 +785,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) int natts; Datum *values; bool *isnull; - IndexScanDesc scan; - HeapTuple tuple; + IndexScanDesc indexScan; + HeapScanDesc heapScan; bool use_wal; TransactionId OldestXmin; TransactionId FreezeXid; @@ -780,7 +797,10 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) */ NewHeap = heap_open(OIDNewHeap, AccessExclusiveLock); OldHeap = heap_open(OIDOldHeap, AccessExclusiveLock); - OldIndex = index_open(OIDOldIndex, AccessExclusiveLock); + if (OidIsValid(OIDOldIndex)) + OldIndex = index_open(OIDOldIndex, AccessExclusiveLock); + else + OldIndex = NULL; /* * Their tuple descriptors should be exactly alike, but here we only need @@ -809,8 +829,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) * freeze_min_age to avoid having CLUSTER freeze tuples earlier than a * plain VACUUM would. */ - vacuum_set_xid_limits(-1, -1, OldHeap->rd_rel->relisshared, - &OldestXmin, &FreezeXid, NULL); + vacuum_set_xid_limits(freeze_min_age, freeze_table_age, + OldHeap->rd_rel->relisshared, &OldestXmin, &FreezeXid, NULL); /* * FreezeXid will become the table's new relfrozenxid, and that mustn't go @@ -828,25 +848,46 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) * copied, we scan with SnapshotAny and use HeapTupleSatisfiesVacuum for * the visibility test. */ - scan = index_beginscan(OldHeap, OldIndex, + if (OldIndex != NULL) + indexScan = index_beginscan(OldHeap, OldIndex, SnapshotAny, 0, (ScanKey) NULL); + else + heapScan = heap_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL); - while ((tuple = index_getnext(scan, ForwardScanDirection)) != NULL) + for (;;) { + HeapTuple tuple; HeapTuple copiedTuple; + Buffer buf; bool isdead; int i; CHECK_FOR_INTERRUPTS(); - /* Since we used no scan keys, should never need to recheck */ - if (scan->xs_recheck) - elog(ERROR, "CLUSTER does not support lossy index conditions"); + if (OldIndex != NULL) + { + tuple = index_getnext(indexScan, ForwardScanDirection); + if (tuple == NULL) + break; - LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE); + /* Since we used no scan keys, should never need to recheck */ + if (indexScan->xs_recheck) + elog(ERROR, "CLUSTER does not support lossy index conditions"); - switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin, - scan->xs_cbuf)) + buf = indexScan->xs_cbuf; + } + else + { + tuple = heap_getnext(heapScan, ForwardScanDirection); + if (tuple == NULL) + break; + + buf = heapScan->rs_cbuf; + } + + LockBuffer(buf, BUFFER_LOCK_SHARE); + + switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin, buf)) { case HEAPTUPLE_DEAD: /* Definitely dead */ @@ -888,7 +929,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) break; } - LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); + LockBuffer(buf, BUFFER_LOCK_UNLOCK); if (isdead) { @@ -932,7 +973,10 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) heap_freetuple(copiedTuple); } - index_endscan(scan); + if (OldIndex != NULL) + index_endscan(indexScan); + else + heap_endscan(heapScan); /* Write out any remaining tuples, and fsync if needed */ end_heap_rewrite(rwstate); @@ -940,7 +984,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) pfree(values); pfree(isnull); - index_close(OldIndex, NoLock); + if (OldIndex != NULL) + index_close(OldIndex, NoLock); heap_close(OldHeap, NoLock); heap_close(NewHeap, NoLock); diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 0078ec7d89d685299710ba4f5eb72220f65af262..850680950e53f71f0afed84c0c5293eef27e9b42 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.402 2010/01/02 16:57:40 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.403 2010/01/06 05:31:13 itagaki Exp $ * *------------------------------------------------------------------------- */ @@ -29,10 +29,12 @@ #include "access/visibilitymap.h" #include "access/xact.h" #include "access/xlog.h" +#include "catalog/catalog.h" #include "catalog/namespace.h" #include "catalog/pg_database.h" #include "catalog/pg_namespace.h" #include "catalog/storage.h" +#include "commands/cluster.h" #include "commands/dbcommands.h" #include "commands/vacuum.h" #include "executor/executor.h" @@ -302,6 +304,8 @@ vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, Assert((vacstmt->options & VACOPT_VACUUM) || !(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE))); Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL); + Assert((vacstmt->options & VACOPT_FULL) || + !(vacstmt->options & VACOPT_INPLACE)); stmttype = (vacstmt->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE"; @@ -1178,12 +1182,24 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound, save_nestlevel = NewGUCNestLevel(); /* - * Do the actual work --- either FULL or "lazy" vacuum + * Do the actual work --- either FULL, FULL INPLACE, or "lazy" vacuum. + * We can use only FULL INPLACE vacuum for system relations. */ - if (vacstmt->options & VACOPT_FULL) + if (!(vacstmt->options & VACOPT_FULL)) + heldoff = lazy_vacuum_rel(onerel, vacstmt, vac_strategy, scanned_all); + else if ((vacstmt->options & VACOPT_INPLACE) || IsSystemRelation(onerel)) heldoff = full_vacuum_rel(onerel, vacstmt); else - heldoff = lazy_vacuum_rel(onerel, vacstmt, vac_strategy, scanned_all); + { + /* close relation before clustering, but hold lock until commit */ + relation_close(onerel, NoLock); + onerel = NULL; + + cluster_rel(relid, InvalidOid, false, + (vacstmt->options & VACOPT_VERBOSE) != 0, + vacstmt->freeze_min_age, vacstmt->freeze_table_age); + heldoff = false; + } /* Roll back any GUC changes executed by index functions */ AtEOXact_GUC(false, save_nestlevel); @@ -1192,7 +1208,8 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound, SetUserIdAndSecContext(save_userid, save_sec_context); /* all done with this class, but hold lock until commit */ - relation_close(onerel, NoLock); + if (onerel) + relation_close(onerel, NoLock); /* * Complete the transaction and free all temporary memory used. diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2ea08893f3b3bba0d9b8c0d4fd7c16d489cb883e..af26d80863305366b0fe2fc30e64254a952d0d25 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.702 2010/01/05 21:53:58 rhaas Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.703 2010/01/06 05:31:13 itagaki Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -490,7 +490,7 @@ static TypeName *TableFuncTypeName(List *columns); IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P - INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER + INNER_P INOUT INPLACE INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION JOIN @@ -6791,6 +6791,7 @@ vacuum_option_elem: | VERBOSE { $$ = VACOPT_VERBOSE; } | FREEZE { $$ = VACOPT_FREEZE; } | FULL { $$ = VACOPT_FULL; } + | FULL INPLACE { $$ = VACOPT_FULL | VACOPT_INPLACE; } ; AnalyzeStmt: @@ -10781,6 +10782,7 @@ unreserved_keyword: | INHERIT | INHERITS | INLINE_P + | INPLACE | INPUT_P | INSENSITIVE | INSERT diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c index 658284da8608a3c43dc189ed27f0f7be0bb544f8..281e32db5b3bd3faf7f04561e5a5bfbdeeca9403 100644 --- a/src/bin/scripts/vacuumdb.c +++ b/src/bin/scripts/vacuumdb.c @@ -5,7 +5,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/bin/scripts/vacuumdb.c,v 1.29 2010/01/06 02:59:46 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/scripts/vacuumdb.c,v 1.30 2010/01/06 05:31:14 itagaki Exp $ * *------------------------------------------------------------------------- */ @@ -14,12 +14,12 @@ #include "common.h" -static void vacuum_one_database(const char *dbname, bool full, bool verbose, +static void vacuum_one_database(const char *dbname, bool full, bool inplace, bool verbose, bool and_analyze, bool only_analyze, bool freeze, const char *table, const char *host, const char *port, const char *username, enum trivalue prompt_password, const char *progname, bool echo); -static void vacuum_all_databases(bool full, bool verbose, bool and_analyze, +static void vacuum_all_databases(bool full, bool inplace, bool verbose, bool and_analyze, bool only_analyze, bool freeze, const char *host, const char *port, const char *username, enum trivalue prompt_password, @@ -47,6 +47,7 @@ main(int argc, char *argv[]) {"table", required_argument, NULL, 't'}, {"full", no_argument, NULL, 'f'}, {"verbose", no_argument, NULL, 'v'}, + {"inplace", no_argument, NULL, 'i'}, {NULL, 0, NULL, 0} }; @@ -68,13 +69,14 @@ main(int argc, char *argv[]) char *table = NULL; bool full = false; bool verbose = false; + bool inplace = false; progname = get_progname(argv[0]); set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts")); handle_help_version_opts(argc, argv, "vacuumdb", help); - while ((c = getopt_long(argc, argv, "h:p:U:wWeqd:zaFt:fv", long_options, &optindex)) != -1) + while ((c = getopt_long(argc, argv, "h:p:U:wWeqd:zaFt:fiv", long_options, &optindex)) != -1) { switch (c) { @@ -120,6 +122,9 @@ main(int argc, char *argv[]) case 'f': full = true; break; + case 'i': + inplace = true; + break; case 'v': verbose = true; break; @@ -143,7 +148,12 @@ main(int argc, char *argv[]) exit(1); } - setup_cancel_handler(); + if (inplace && !full) + { + fprintf(stderr, _("%s: cannot use the \"inplace\" option when performing full vacuum\n"), + progname); + exit(1); + } if (only_analyze) { @@ -162,6 +172,8 @@ main(int argc, char *argv[]) /* ignore 'and_analyze' */ } + setup_cancel_handler(); + if (alldb) { if (dbname) @@ -177,7 +189,7 @@ main(int argc, char *argv[]) exit(1); } - vacuum_all_databases(full, verbose, and_analyze, only_analyze, freeze, + vacuum_all_databases(full, inplace, verbose, and_analyze, only_analyze, freeze, host, port, username, prompt_password, progname, echo, quiet); } @@ -193,7 +205,7 @@ main(int argc, char *argv[]) dbname = get_user_name(progname); } - vacuum_one_database(dbname, full, verbose, and_analyze, only_analyze, + vacuum_one_database(dbname, full, inplace, verbose, and_analyze, only_analyze, freeze, table, host, port, username, prompt_password, progname, echo); @@ -204,7 +216,7 @@ main(int argc, char *argv[]) static void -vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyze, +vacuum_one_database(const char *dbname, bool full, bool inplace, bool verbose, bool and_analyze, bool only_analyze, bool freeze, const char *table, const char *host, const char *port, const char *username, enum trivalue prompt_password, @@ -216,25 +228,67 @@ vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyz initPQExpBuffer(&sql); + conn = connectDatabase(dbname, host, port, username, prompt_password, progname); + if (only_analyze) + { appendPQExpBuffer(&sql, "ANALYZE"); + if (verbose) + appendPQExpBuffer(&sql, " VERBOSE"); + } else { appendPQExpBuffer(&sql, "VACUUM"); - if (full) - appendPQExpBuffer(&sql, " FULL"); - if (freeze) - appendPQExpBuffer(&sql, " FREEZE"); - if (and_analyze) - appendPQExpBuffer(&sql, " ANALYZE"); + if (PQserverVersion(conn) >= 80500) + { + const char *paren = " ("; + const char *comma = ", "; + const char *sep = paren; + + if (full) + { + appendPQExpBuffer(&sql, "%sFULL%s", sep, + inplace ? " INPLACE" : ""); + sep = comma; + } + if (freeze) + { + appendPQExpBuffer(&sql, "%sFREEZE", sep); + sep = comma; + } + if (verbose) + { + appendPQExpBuffer(&sql, "%sVERBOSE", sep); + sep = comma; + } + if (and_analyze) + { + appendPQExpBuffer(&sql, "%sANALYZE", sep); + sep = comma; + } + if (sep != paren) + appendPQExpBuffer(&sql, ")"); + } + else + { + /* + * On older servers, VACUUM FULL is equivalent to VACUUM (FULL + * INPLACE) on newer servers, so we can ignore 'inplace'. + */ + if (full) + appendPQExpBuffer(&sql, " FULL"); + if (freeze) + appendPQExpBuffer(&sql, " FREEZE"); + if (verbose) + appendPQExpBuffer(&sql, " VERBOSE"); + if (and_analyze) + appendPQExpBuffer(&sql, " ANALYZE"); + } } - if (verbose) - appendPQExpBuffer(&sql, " VERBOSE"); if (table) appendPQExpBuffer(&sql, " %s", table); appendPQExpBuffer(&sql, ";\n"); - conn = connectDatabase(dbname, host, port, username, prompt_password, progname); if (!executeMaintenanceCommand(conn, sql.data, echo)) { if (table) @@ -252,7 +306,7 @@ vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyz static void -vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool only_analyze, +vacuum_all_databases(bool full, bool inplace, bool verbose, bool and_analyze, bool only_analyze, bool freeze, const char *host, const char *port, const char *username, enum trivalue prompt_password, const char *progname, bool echo, bool quiet) @@ -275,7 +329,7 @@ vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool only_analyz fflush(stdout); } - vacuum_one_database(dbname, full, verbose, and_analyze, only_analyze, + vacuum_one_database(dbname, full, inplace, verbose, and_analyze, only_analyze, freeze, NULL, host, port, username, prompt_password, progname, echo); } @@ -296,6 +350,7 @@ help(const char *progname) printf(_(" -e, --echo show the commands being sent to the server\n")); printf(_(" -f, --full do full vacuuming\n")); printf(_(" -F, --freeze freeze row transaction information\n")); + printf(_(" -i, --inplace do full inplace vacuuming\n")); printf(_(" -o, --only-analyze only update optimizer hints\n")); printf(_(" -q, --quiet don't write any messages\n")); printf(_(" -t, --table='TABLE[(COLUMNS)]' vacuum specific table only\n")); diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index dd7a1caf1015fb4e176f65a220ea0f7208592870..c58013c640344ef86e2d0945113b8abb792b84de 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.37 2010/01/02 16:58:03 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.38 2010/01/06 05:31:14 itagaki Exp $ * *------------------------------------------------------------------------- */ @@ -18,7 +18,8 @@ extern void cluster(ClusterStmt *stmt, bool isTopLevel); - +extern void cluster_rel(Oid tableOid, Oid indexOid, bool recheck, + bool verbose, int freeze_min_age, int freeze_table_age); extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck); extern void mark_index_clustered(Relation rel, Oid indexOid); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 18673ec30d8a11fd42dc0b72119e25c692c0e9f9..9a0bb8eec3f8a734f46e7fc1390f0d369e2b47ac 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -13,7 +13,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.422 2010/01/05 21:53:59 rhaas Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.423 2010/01/06 05:31:14 itagaki Exp $ * *------------------------------------------------------------------------- */ @@ -2244,7 +2244,8 @@ typedef enum VacuumOption VACOPT_ANALYZE = 1 << 1, /* do ANALYZE */ VACOPT_VERBOSE = 1 << 2, /* print progress info */ VACOPT_FREEZE = 1 << 3, /* FREEZE option */ - VACOPT_FULL = 1 << 4 /* FULL (non-concurrent) vacuum */ + VACOPT_FULL = 1 << 4, /* FULL (non-concurrent) vacuum */ + VACOPT_INPLACE = 1 << 5 /* traditional FULL INPLACE vacuum */ } VacuumOption; typedef struct VacuumStmt diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 72d717bfda87afa93f4c85cdfa60848aeb61b46e..b7d647ab279e54ce65356265e86e33a2939ba433 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -11,7 +11,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.9 2010/01/02 16:58:07 momjian Exp $ + * $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.10 2010/01/06 05:31:14 itagaki Exp $ * *------------------------------------------------------------------------- */ @@ -193,6 +193,7 @@ PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD) PG_KEYWORD("inline", INLINE_P, UNRESERVED_KEYWORD) PG_KEYWORD("inner", INNER_P, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("inout", INOUT, COL_NAME_KEYWORD) +PG_KEYWORD("inplace", INPLACE, UNRESERVED_KEYWORD) PG_KEYWORD("input", INPUT_P, UNRESERVED_KEYWORD) PG_KEYWORD("insensitive", INSENSITIVE, UNRESERVED_KEYWORD) PG_KEYWORD("insert", INSERT, UNRESERVED_KEYWORD) diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out index 1a139d03773e920f0889fa72459888a978729db3..e94e1d538f802044fed614e822074f239479c8b4 100644 --- a/src/test/regress/expected/vacuum.out +++ b/src/test/regress/expected/vacuum.out @@ -57,4 +57,65 @@ SELECT * FROM vactst; (0 rows) VACUUM (FULL, FREEZE) vactst; +VACUUM (ANALYZE, FULL INPLACE) vactst; +CREATE TABLE vaccluster (i INT PRIMARY KEY); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "vaccluster_pkey" for table "vaccluster" +ALTER TABLE vaccluster CLUSTER ON vaccluster_pkey; +INSERT INTO vaccluster SELECT * FROM vactst; +CREATE TEMP TABLE vacid ( + relid regclass, + filenode_0 oid, + filenode_1 oid, + filenode_2 oid, + filenode_3 oid +); +INSERT INTO vacid (relid, filenode_0) +SELECT oid, relfilenode FROM pg_class WHERE oid::regclass IN ( + 'pg_am', -- normal catalog + 'pg_class', -- fundamental catalog + 'pg_database', -- shared catalog + 'vaccluster' , -- clustered table + 'vacid', -- temp table + 'vactst' -- normal table +); +-- only clusterd table should be changed +CLUSTER vaccluster; +UPDATE vacid SET filenode_1 = relfilenode + FROM pg_class WHERE oid = relid; +-- all tables should not be changed +VACUUM (FULL INPLACE) pg_am; +VACUUM (FULL INPLACE) pg_class; +VACUUM (FULL INPLACE) pg_database; +VACUUM (FULL INPLACE) vaccluster; +VACUUM (FULL INPLACE) vacid; +VACUUM (FULL INPLACE) vactst; +UPDATE vacid SET filenode_2 = relfilenode + FROM pg_class WHERE oid = relid; +-- only non-system tables should be changed +VACUUM FULL pg_am; +VACUUM FULL pg_class; +VACUUM FULL pg_database; +VACUUM FULL vaccluster; +VACUUM FULL vacid; +VACUUM FULL vactst; +UPDATE vacid SET filenode_3 = relfilenode + FROM pg_class WHERE oid = relid; +SELECT relid, + filenode_0 = filenode_1 AS cluster, + filenode_1 = filenode_2 AS full_inplace, + filenode_2 = filenode_3 AS full + FROM vacid + ORDER BY relid::text; + relid | cluster | full_inplace | full +-------------+---------+--------------+------ + pg_am | t | t | t + pg_class | t | t | t + pg_database | t | t | t + vaccluster | f | t | f + vacid | t | t | f + vactst | t | t | f +(6 rows) + +DROP TABLE vaccluster; +DROP TABLE vacid; DROP TABLE vactst; diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql index e1686971c967427971cb5382f2b6a2b3b7c119f8..87772ad5f6d6c3c7d414e6116ea8465d04edd502 100644 --- a/src/test/regress/sql/vacuum.sql +++ b/src/test/regress/sql/vacuum.sql @@ -40,5 +40,62 @@ DELETE FROM vactst; SELECT * FROM vactst; VACUUM (FULL, FREEZE) vactst; +VACUUM (ANALYZE, FULL INPLACE) vactst; +CREATE TABLE vaccluster (i INT PRIMARY KEY); +ALTER TABLE vaccluster CLUSTER ON vaccluster_pkey; +INSERT INTO vaccluster SELECT * FROM vactst; + +CREATE TEMP TABLE vacid ( + relid regclass, + filenode_0 oid, + filenode_1 oid, + filenode_2 oid, + filenode_3 oid +); + +INSERT INTO vacid (relid, filenode_0) +SELECT oid, relfilenode FROM pg_class WHERE oid::regclass IN ( + 'pg_am', -- normal catalog + 'pg_class', -- fundamental catalog + 'pg_database', -- shared catalog + 'vaccluster' , -- clustered table + 'vacid', -- temp table + 'vactst' -- normal table +); + +-- only clusterd table should be changed +CLUSTER vaccluster; +UPDATE vacid SET filenode_1 = relfilenode + FROM pg_class WHERE oid = relid; + +-- all tables should not be changed +VACUUM (FULL INPLACE) pg_am; +VACUUM (FULL INPLACE) pg_class; +VACUUM (FULL INPLACE) pg_database; +VACUUM (FULL INPLACE) vaccluster; +VACUUM (FULL INPLACE) vacid; +VACUUM (FULL INPLACE) vactst; +UPDATE vacid SET filenode_2 = relfilenode + FROM pg_class WHERE oid = relid; + +-- only non-system tables should be changed +VACUUM FULL pg_am; +VACUUM FULL pg_class; +VACUUM FULL pg_database; +VACUUM FULL vaccluster; +VACUUM FULL vacid; +VACUUM FULL vactst; +UPDATE vacid SET filenode_3 = relfilenode + FROM pg_class WHERE oid = relid; + +SELECT relid, + filenode_0 = filenode_1 AS cluster, + filenode_1 = filenode_2 AS full_inplace, + filenode_2 = filenode_3 AS full + FROM vacid + ORDER BY relid::text; + +DROP TABLE vaccluster; +DROP TABLE vacid; DROP TABLE vactst;