diff -cpr head/src/backend/commands/analyze.c work/src/backend/commands/analyze.c *** head/src/backend/commands/analyze.c 2009-08-13 03:23:49.000000000 +0900 --- work/src/backend/commands/analyze.c 2009-10-27 10:15:06.817275251 +0900 *************** analyze_rel(Oid relid, VacuumStmt *vacst *** 135,141 **** Oid save_userid; bool save_secdefcxt; ! if (vacstmt->verbose) elevel = INFO; else elevel = DEBUG2; --- 135,141 ---- Oid save_userid; bool save_secdefcxt; ! if (vacstmt->options & VACOPT_VERBOSE) elevel = INFO; else elevel = DEBUG2; *************** analyze_rel(Oid relid, VacuumStmt *vacst *** 173,179 **** (pg_database_ownercheck(MyDatabaseId, GetUserId()) && !onerel->rd_rel->relisshared))) { /* No need for a WARNING if we already complained during VACUUM */ ! if (!vacstmt->vacuum) { if (onerel->rd_rel->relisshared) ereport(WARNING, --- 173,179 ---- (pg_database_ownercheck(MyDatabaseId, GetUserId()) && !onerel->rd_rel->relisshared))) { /* No need for a WARNING if we already complained during VACUUM */ ! if (!(vacstmt->options & VACOPT_VACUUM)) { if (onerel->rd_rel->relisshared) ereport(WARNING, *************** analyze_rel(Oid relid, VacuumStmt *vacst *** 199,205 **** if (onerel->rd_rel->relkind != RELKIND_RELATION) { /* No need for a WARNING if we already complained during VACUUM */ ! if (!vacstmt->vacuum) ereport(WARNING, (errmsg("skipping \"%s\" --- cannot analyze indexes, views, or special system tables", RelationGetRelationName(onerel)))); --- 199,205 ---- if (onerel->rd_rel->relkind != RELKIND_RELATION) { /* No need for a WARNING if we already complained during VACUUM */ ! if (!(vacstmt->options & VACOPT_VACUUM)) ereport(WARNING, (errmsg("skipping \"%s\" --- cannot analyze indexes, views, or special system tables", RelationGetRelationName(onerel)))); *************** analyze_rel(Oid relid, VacuumStmt *vacst *** 475,481 **** * VACUUM ANALYZE, don't overwrite the accurate count already inserted by * VACUUM. */ ! if (!vacstmt->vacuum) { for (ind = 0; ind < nindexes; ind++) { --- 475,481 ---- * VACUUM ANALYZE, don't overwrite the accurate count already inserted by * VACUUM. */ ! if (!(vacstmt->options & VACOPT_VACUUM)) { for (ind = 0; ind < nindexes; ind++) { *************** analyze_rel(Oid relid, VacuumStmt *vacst *** 493,499 **** cleanup: /* If this isn't part of VACUUM ANALYZE, let index AMs do cleanup */ ! if (!vacstmt->vacuum) { for (ind = 0; ind < nindexes; ind++) { --- 493,499 ---- cleanup: /* If this isn't part of VACUUM ANALYZE, let index AMs do cleanup */ ! if (!(vacstmt->options & VACOPT_VACUUM)) { for (ind = 0; ind < nindexes; ind++) { diff -cpr head/src/backend/commands/cluster.c work/src/backend/commands/cluster.c *** head/src/backend/commands/cluster.c 2009-10-06 04:24:36.000000000 +0900 --- work/src/backend/commands/cluster.c 2009-10-27 12:58:36.218261609 +0900 *************** typedef struct *** 61,69 **** } 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 List *get_tables_to_cluster(MemoryContext cluster_context); --- 61,70 ---- } RelToCluster; ! 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); *************** cluster(ClusterStmt *stmt, bool isTopLev *** 101,107 **** Oid tableOid, indexOid = InvalidOid; Relation rel; - RelToCluster rvtc; /* Find and lock the table */ rel = heap_openrv(stmt->relation, AccessExclusiveLock); --- 102,107 ---- *************** cluster(ClusterStmt *stmt, bool isTopLev *** 169,183 **** 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); } else { --- 169,179 ---- stmt->indexname, stmt->relation->relname))); } /* close relation, keep lock till commit */ heap_close(rel, NoLock); /* Do the job */ ! cluster_rel(tableOid, indexOid, false, stmt->verbose, -1, -1); } else { *************** cluster(ClusterStmt *stmt, bool isTopLev *** 226,232 **** StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); ! cluster_rel(rvtc, true, stmt->verbose); PopActiveSnapshot(); CommitTransactionCommand(); } --- 222,228 ---- StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); ! cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose, -1, -1); PopActiveSnapshot(); CommitTransactionCommand(); } *************** cluster(ClusterStmt *stmt, bool isTopLev *** 253,260 **** * the new table, it's better to create the indexes afterwards than to fill * them incrementally while we load the table. */ ! static void ! cluster_rel(RelToCluster *rvtc, bool recheck, bool verbose) { Relation OldHeap; --- 249,257 ---- * the new table, it's better to create the indexes afterwards than to fill * them incrementally while we load the table. */ ! void ! cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, ! int freeze_min_age, int freeze_table_age) { Relation OldHeap; *************** cluster_rel(RelToCluster *rvtc, bool rec *** 267,273 **** * case, since cluster() already did it.) The index lock is taken inside * check_index_is_clusterable. */ ! OldHeap = try_relation_open(rvtc->tableOid, AccessExclusiveLock); /* If the table has gone away, we can skip processing it */ if (!OldHeap) --- 264,270 ---- * case, since cluster() already did it.) The index lock is taken inside * check_index_is_clusterable. */ ! OldHeap = try_relation_open(tableOid, AccessExclusiveLock); /* If the table has gone away, we can skip processing it */ if (!OldHeap) *************** cluster_rel(RelToCluster *rvtc, bool rec *** 287,293 **** Form_pg_index indexForm; /* Check that the user still owns the relation */ ! if (!pg_class_ownercheck(rvtc->tableOid, GetUserId())) { relation_close(OldHeap, AccessExclusiveLock); return; --- 284,290 ---- Form_pg_index indexForm; /* Check that the user still owns the relation */ ! if (!pg_class_ownercheck(tableOid, GetUserId())) { relation_close(OldHeap, AccessExclusiveLock); return; *************** cluster_rel(RelToCluster *rvtc, bool rec *** 308,360 **** return; } ! /* ! * Check that the index still exists ! */ ! if (!SearchSysCacheExists(RELOID, ! ObjectIdGetDatum(rvtc->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) ! { ReleaseSysCache(tuple); - relation_close(OldHeap, AccessExclusiveLock); - return; } - ReleaseSysCache(tuple); } ! /* Check index is valid to cluster on */ ! check_index_is_clusterable(OldHeap, rvtc->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); /* NB: rebuild_relation does heap_close() on OldHeap */ } /* ! * Verify that the specified index is a legitimate index 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 --- 305,360 ---- return; } ! if (OidIsValid(indexOid)) { ! /* ! * 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(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); } } ! /* 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, indexOid, freeze_min_age, freeze_table_age); /* NB: rebuild_relation does heap_close() on OldHeap */ } /* ! * 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 *************** check_index_is_clusterable(Relation OldH *** 366,371 **** --- 366,403 ---- { 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); /* *************** check_index_is_clusterable(Relation OldH *** 448,481 **** 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); } --- 480,485 ---- *************** mark_index_clustered(Relation rel, Oid i *** 565,571 **** * NB: this routine closes OldHeap at the right time; caller should not. */ static void ! rebuild_relation(Relation OldHeap, Oid indexOid) { Oid tableOid = RelationGetRelid(OldHeap); Oid tableSpace = OldHeap->rd_rel->reltablespace; --- 569,576 ---- * NB: this routine closes OldHeap at the right time; caller should not. */ static void ! rebuild_relation(Relation OldHeap, Oid indexOid, ! int freeze_min_age, int freeze_table_age) { Oid tableOid = RelationGetRelid(OldHeap); Oid tableSpace = OldHeap->rd_rel->reltablespace; *************** rebuild_relation(Relation OldHeap, Oid i *** 576,582 **** Relation newrel; /* Mark the correct index as clustered */ ! mark_index_clustered(OldHeap, indexOid); /* Close relcache entry, but keep lock until transaction commit */ heap_close(OldHeap, NoLock); --- 581,588 ---- Relation newrel; /* Mark the correct index as clustered */ ! if (OidIsValid(indexOid)) ! mark_index_clustered(OldHeap, indexOid); /* Close relcache entry, but keep lock until transaction commit */ heap_close(OldHeap, NoLock); *************** rebuild_relation(Relation OldHeap, Oid i *** 599,605 **** /* * Copy the heap data into the new table in the desired order. */ ! frozenXid = copy_heap_data(OIDNewHeap, tableOid, indexOid); /* To make the new heap's data visible (probably not needed?). */ CommandCounterIncrement(); --- 605,612 ---- /* * Copy the heap data into the new table in the desired order. */ ! 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(); *************** make_new_heap(Oid OIDOldHeap, const char *** 758,764 **** * freeze cutoff point for the tuples. */ static TransactionId ! copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) { Relation NewHeap, OldHeap, --- 765,772 ---- * freeze cutoff point for the tuples. */ static TransactionId ! copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, ! int freeze_min_age, int freeze_table_age) { Relation NewHeap, OldHeap, *************** copy_heap_data(Oid OIDNewHeap, Oid OIDOl *** 768,775 **** int natts; Datum *values; bool *isnull; ! IndexScanDesc scan; ! HeapTuple tuple; bool use_wal; TransactionId OldestXmin; TransactionId FreezeXid; --- 776,783 ---- int natts; Datum *values; bool *isnull; ! IndexScanDesc indexScan; ! HeapScanDesc heapScan; bool use_wal; TransactionId OldestXmin; TransactionId FreezeXid; *************** copy_heap_data(Oid OIDNewHeap, Oid OIDOl *** 780,786 **** */ NewHeap = heap_open(OIDNewHeap, AccessExclusiveLock); OldHeap = heap_open(OIDOldHeap, AccessExclusiveLock); ! OldIndex = index_open(OIDOldIndex, AccessExclusiveLock); /* * Their tuple descriptors should be exactly alike, but here we only need --- 788,797 ---- */ NewHeap = heap_open(OIDNewHeap, AccessExclusiveLock); OldHeap = heap_open(OIDOldHeap, AccessExclusiveLock); ! if (OidIsValid(OIDOldIndex)) ! OldIndex = index_open(OIDOldIndex, AccessExclusiveLock); ! else ! OldIndex = NULL; /* * Their tuple descriptors should be exactly alike, but here we only need *************** copy_heap_data(Oid OIDNewHeap, Oid OIDOl *** 809,816 **** * 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); /* * FreezeXid will become the table's new relfrozenxid, and that mustn't go --- 820,827 ---- * freeze_min_age to avoid having CLUSTER freeze tuples earlier than a * plain VACUUM would. */ ! 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 *************** copy_heap_data(Oid OIDNewHeap, Oid OIDOl *** 828,852 **** * copied, we scan with SnapshotAny and use HeapTupleSatisfiesVacuum for * the visibility test. */ ! scan = index_beginscan(OldHeap, OldIndex, SnapshotAny, 0, (ScanKey) NULL); ! while ((tuple = index_getnext(scan, ForwardScanDirection)) != NULL) { HeapTuple copiedTuple; 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"); ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE); ! switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin, ! scan->xs_cbuf)) { case HEAPTUPLE_DEAD: /* Definitely dead */ --- 839,884 ---- * copied, we scan with SnapshotAny and use HeapTupleSatisfiesVacuum for * the visibility test. */ ! if (OldIndex != NULL) ! indexScan = index_beginscan(OldHeap, OldIndex, SnapshotAny, 0, (ScanKey) NULL); + else + heapScan = heap_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL); ! for (;;) { + HeapTuple tuple; HeapTuple copiedTuple; + Buffer buf; bool isdead; int i; CHECK_FOR_INTERRUPTS(); ! if (OldIndex != NULL) ! { ! tuple = index_getnext(indexScan, ForwardScanDirection); ! if (tuple == NULL) ! break; ! /* Since we used no scan keys, should never need to recheck */ ! if (indexScan->xs_recheck) ! elog(ERROR, "CLUSTER does not support lossy index conditions"); ! 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 */ *************** copy_heap_data(Oid OIDNewHeap, Oid OIDOl *** 888,894 **** break; } ! LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); if (isdead) { --- 920,926 ---- break; } ! LockBuffer(buf, BUFFER_LOCK_UNLOCK); if (isdead) { *************** copy_heap_data(Oid OIDNewHeap, Oid OIDOl *** 932,938 **** heap_freetuple(copiedTuple); } ! index_endscan(scan); /* Write out any remaining tuples, and fsync if needed */ end_heap_rewrite(rwstate); --- 964,973 ---- heap_freetuple(copiedTuple); } ! if (OldIndex != NULL) ! index_endscan(indexScan); ! else ! heap_endscan(heapScan); /* Write out any remaining tuples, and fsync if needed */ end_heap_rewrite(rwstate); *************** copy_heap_data(Oid OIDNewHeap, Oid OIDOl *** 940,946 **** pfree(values); pfree(isnull); ! index_close(OldIndex, NoLock); heap_close(OldHeap, NoLock); heap_close(NewHeap, NoLock); --- 975,982 ---- pfree(values); pfree(isnull); ! if (OldIndex != NULL) ! index_close(OldIndex, NoLock); heap_close(OldHeap, NoLock); heap_close(NewHeap, NoLock); diff -cpr head/src/backend/commands/vacuum.c work/src/backend/commands/vacuum.c *** head/src/backend/commands/vacuum.c 2009-10-26 11:26:29.000000000 +0900 --- work/src/backend/commands/vacuum.c 2009-10-27 13:24:51.283271344 +0900 *************** *** 29,38 **** --- 29,40 ---- #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" *************** void *** 289,302 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel) { ! const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE"; volatile MemoryContext anl_context = NULL; volatile bool all_rels, in_outer_xact, use_own_xacts; List *relations; ! if (vacstmt->verbose) elevel = INFO; else elevel = DEBUG2; --- 291,319 ---- vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel) { ! const char *stmttype; volatile MemoryContext anl_context = NULL; volatile bool all_rels, in_outer_xact, use_own_xacts; List *relations; ! /* Adjust options. */ ! if (vacstmt->options & (VACOPT_INPLACE | VACOPT_REPLACE | VACOPT_FREEZE)) ! vacstmt->options |= VACOPT_VACUUM; ! if (vacstmt->va_cols) ! vacstmt->options |= VACOPT_ANALYZE; ! ! Assert(vacstmt->options & (VACOPT_VACUUM | VACOPT_ANALYZE)); ! stmttype = (vacstmt->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE"; ! ! if (vacstmt->options & VACOPT_FREEZE) ! { ! vacstmt->freeze_min_age = 0; ! vacstmt->freeze_table_age = 0; ! } ! ! if (vacstmt->options & VACOPT_VERBOSE) elevel = INFO; else elevel = DEBUG2; *************** vacuum(VacuumStmt *vacstmt, Oid relid, b *** 315,336 **** * * ANALYZE (without VACUUM) can run either way. */ ! if (vacstmt->vacuum) { PreventTransactionChain(isTopLevel, stmttype); in_outer_xact = false; } else in_outer_xact = IsInTransactionChain(isTopLevel); /* - * Send info about dead objects to the statistics collector, unless we are - * in autovacuum --- autovacuum.c does this for itself. - */ - if (vacstmt->vacuum && !IsAutoVacuumWorkerProcess()) - pgstat_vacuum_stat(); - - /* * Create special memory context for cross-transaction storage. * * Since it is a child of PortalContext, it will go away eventually even --- 332,353 ---- * * ANALYZE (without VACUUM) can run either way. */ ! if (vacstmt->options & VACOPT_VACUUM) { PreventTransactionChain(isTopLevel, stmttype); in_outer_xact = false; + + /* + * Send info about dead objects to the statistics collector, unless we are + * in autovacuum --- autovacuum.c does this for itself. + */ + if (!IsAutoVacuumWorkerProcess()) + pgstat_vacuum_stat(); } else in_outer_xact = IsInTransactionChain(isTopLevel); /* * Create special memory context for cross-transaction storage. * * Since it is a child of PortalContext, it will go away eventually even *************** vacuum(VacuumStmt *vacstmt, Oid relid, b *** 378,388 **** * transaction block, and also in an autovacuum worker, use own * transactions so we can release locks sooner. */ ! if (vacstmt->vacuum) use_own_xacts = true; else { ! Assert(vacstmt->analyze); if (IsAutoVacuumWorkerProcess()) use_own_xacts = true; else if (in_outer_xact) --- 395,405 ---- * transaction block, and also in an autovacuum worker, use own * transactions so we can release locks sooner. */ ! if (vacstmt->options & VACOPT_VACUUM) use_own_xacts = true; else { ! Assert(vacstmt->options & VACOPT_ANALYZE); if (IsAutoVacuumWorkerProcess()) use_own_xacts = true; else if (in_outer_xact) *************** vacuum(VacuumStmt *vacstmt, Oid relid, b *** 438,448 **** Oid relid = lfirst_oid(cur); bool scanned_all = false; ! if (vacstmt->vacuum) ! vacuum_rel(relid, vacstmt, do_toast, for_wraparound, ! &scanned_all); ! if (vacstmt->analyze) { MemoryContext old_context = NULL; --- 455,465 ---- Oid relid = lfirst_oid(cur); bool scanned_all = false; ! if (vacstmt->options & VACOPT_VACUUM) ! vacuum_rel(relid, vacstmt, do_toast, for_wraparound, ! &scanned_all); ! if (vacstmt->options & VACOPT_ANALYZE) { MemoryContext old_context = NULL; *************** vacuum(VacuumStmt *vacstmt, Oid relid, b *** 502,508 **** StartTransactionCommand(); } ! if (vacstmt->vacuum && !IsAutoVacuumWorkerProcess()) { /* * Update pg_database.datfrozenxid, and truncate pg_clog if possible. --- 519,525 ---- StartTransactionCommand(); } ! if ((vacstmt->options & VACOPT_VACUUM) && !IsAutoVacuumWorkerProcess()) { /* * Update pg_database.datfrozenxid, and truncate pg_clog if possible. *************** vacuum_rel(Oid relid, VacuumStmt *vacstm *** 1033,1039 **** */ PushActiveSnapshot(GetTransactionSnapshot()); ! if (!vacstmt->full) { /* * In lazy vacuum, we can set the PROC_IN_VACUUM flag, which lets --- 1050,1056 ---- */ PushActiveSnapshot(GetTransactionSnapshot()); ! if (!(vacstmt->options & (VACOPT_INPLACE | VACOPT_REPLACE))) { /* * In lazy vacuum, we can set the PROC_IN_VACUUM flag, which lets *************** vacuum_rel(Oid relid, VacuumStmt *vacstm *** 1073,1079 **** * vacuum, but just ShareUpdateExclusiveLock for concurrent vacuum. Either * way, we can be sure that no other backend is vacuuming the same table. */ ! lmode = vacstmt->full ? AccessExclusiveLock : ShareUpdateExclusiveLock; /* * Open the relation and get the appropriate lock on it. --- 1090,1098 ---- * vacuum, but just ShareUpdateExclusiveLock for concurrent vacuum. Either * way, we can be sure that no other backend is vacuuming the same table. */ ! lmode = (vacstmt->options & VACOPT_INPLACE) ! ? AccessExclusiveLock ! : ShareUpdateExclusiveLock; /* * Open the relation and get the appropriate lock on it. *************** vacuum_rel(Oid relid, VacuumStmt *vacstm *** 1183,1200 **** SetUserIdAndContext(onerel->rd_rel->relowner, true); /* ! * Do the actual work --- either FULL or "lazy" vacuum */ ! if (vacstmt->full) ! full_vacuum_rel(onerel, vacstmt); else ! lazy_vacuum_rel(onerel, vacstmt, vac_strategy, scanned_all); /* Restore userid */ SetUserIdAndContext(save_userid, save_secdefcxt); /* all done with this class, but hold lock until commit */ ! relation_close(onerel, NoLock); /* * Complete the transaction and free all temporary memory used. --- 1202,1244 ---- SetUserIdAndContext(onerel->rd_rel->relowner, true); /* ! * Do the actual work --- either FULL REPLACE, INPLACE, or "lazy" vacuum */ ! if (vacstmt->options & VACOPT_REPLACE) ! { ! if (IsSystemRelation(onerel)) ! { ! /* ! * Since we cannot FULL REPLACE vacuum for system relations, ! * do FULL INPLACE vacuum instead. ! */ ! full_vacuum_rel(onerel, vacstmt); ! } ! else ! { ! /* 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); ! } ! } else ! { ! if (vacstmt->options & VACOPT_INPLACE) ! full_vacuum_rel(onerel, vacstmt); ! else ! lazy_vacuum_rel(onerel, vacstmt, vac_strategy, scanned_all); ! } /* Restore userid */ SetUserIdAndContext(save_userid, save_secdefcxt); /* all done with this class, but hold lock until commit */ ! if (onerel) ! relation_close(onerel, NoLock); /* * Complete the transaction and free all temporary memory used. *************** full_vacuum_rel(Relation onerel, VacuumS *** 1323,1329 **** /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, ! true, vacstmt->analyze, vacrelstats->rel_tuples); } --- 1367,1373 ---- /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, ! true, (vacstmt->options & VACOPT_ANALYZE) != 0, vacrelstats->rel_tuples); } diff -cpr head/src/backend/commands/vacuumlazy.c work/src/backend/commands/vacuumlazy.c *** head/src/backend/commands/vacuumlazy.c 2009-08-24 11:18:32.000000000 +0900 --- work/src/backend/commands/vacuumlazy.c 2009-10-27 10:15:06.820375310 +0900 *************** lazy_vacuum_rel(Relation onerel, VacuumS *** 160,166 **** if (IsAutoVacuumWorkerProcess() && Log_autovacuum_min_duration > 0) starttime = GetCurrentTimestamp(); ! if (vacstmt->verbose) elevel = INFO; else elevel = DEBUG2; --- 160,166 ---- if (IsAutoVacuumWorkerProcess() && Log_autovacuum_min_duration > 0) starttime = GetCurrentTimestamp(); ! if (vacstmt->options & VACOPT_VERBOSE) elevel = INFO; else elevel = DEBUG2; *************** lazy_vacuum_rel(Relation onerel, VacuumS *** 222,228 **** pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, vacrelstats->scanned_all, ! vacstmt->analyze, vacrelstats->rel_tuples); /* and log the action if appropriate */ if (IsAutoVacuumWorkerProcess() && Log_autovacuum_min_duration >= 0) --- 222,229 ---- pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, vacrelstats->scanned_all, ! (vacstmt->options & VACOPT_ANALYZE) != 0, ! vacrelstats->rel_tuples); /* and log the action if appropriate */ if (IsAutoVacuumWorkerProcess() && Log_autovacuum_min_duration >= 0) diff -cpr head/src/backend/nodes/copyfuncs.c work/src/backend/nodes/copyfuncs.c *** head/src/backend/nodes/copyfuncs.c 2009-10-26 11:26:31.000000000 +0900 --- work/src/backend/nodes/copyfuncs.c 2009-10-27 10:15:06.821324102 +0900 *************** _copyVacuumStmt(VacuumStmt *from) *** 2955,2964 **** { VacuumStmt *newnode = makeNode(VacuumStmt); ! COPY_SCALAR_FIELD(vacuum); ! COPY_SCALAR_FIELD(full); ! COPY_SCALAR_FIELD(analyze); ! COPY_SCALAR_FIELD(verbose); COPY_SCALAR_FIELD(freeze_min_age); COPY_SCALAR_FIELD(freeze_table_age); COPY_NODE_FIELD(relation); --- 2955,2961 ---- { VacuumStmt *newnode = makeNode(VacuumStmt); ! COPY_SCALAR_FIELD(options); COPY_SCALAR_FIELD(freeze_min_age); COPY_SCALAR_FIELD(freeze_table_age); COPY_NODE_FIELD(relation); diff -cpr head/src/backend/nodes/equalfuncs.c work/src/backend/nodes/equalfuncs.c *** head/src/backend/nodes/equalfuncs.c 2009-10-26 11:26:31.000000000 +0900 --- work/src/backend/nodes/equalfuncs.c 2009-10-27 10:15:06.822375336 +0900 *************** _equalDropdbStmt(DropdbStmt *a, DropdbSt *** 1482,1491 **** static bool _equalVacuumStmt(VacuumStmt *a, VacuumStmt *b) { ! COMPARE_SCALAR_FIELD(vacuum); ! COMPARE_SCALAR_FIELD(full); ! COMPARE_SCALAR_FIELD(analyze); ! COMPARE_SCALAR_FIELD(verbose); COMPARE_SCALAR_FIELD(freeze_min_age); COMPARE_SCALAR_FIELD(freeze_table_age); COMPARE_NODE_FIELD(relation); --- 1482,1488 ---- static bool _equalVacuumStmt(VacuumStmt *a, VacuumStmt *b) { ! COMPARE_SCALAR_FIELD(options); COMPARE_SCALAR_FIELD(freeze_min_age); COMPARE_SCALAR_FIELD(freeze_table_age); COMPARE_NODE_FIELD(relation); diff -cpr head/src/backend/parser/gram.y work/src/backend/parser/gram.y *** head/src/backend/parser/gram.y 2009-10-15 07:14:22.000000000 +0900 --- work/src/backend/parser/gram.y 2009-10-27 13:23:10.577271273 +0900 *************** static TypeName *TableFuncTypeName(List *** 234,239 **** --- 234,240 ---- transaction_mode_item %type opt_lock lock_type cast_context + %type vacuum_option_list vacuum_option_elem %type opt_force opt_or_replace opt_grant_grant_option opt_grant_admin_option opt_nowait opt_if_exists opt_with_data *************** static TypeName *TableFuncTypeName(List *** 489,495 **** 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 INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION JOIN --- 490,496 ---- IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P ! INNER_P INOUT INPLACE INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION JOIN *************** cluster_index_specification: *** 6625,6636 **** VacuumStmt: VACUUM opt_full opt_freeze opt_verbose { VacuumStmt *n = makeNode(VacuumStmt); ! n->vacuum = true; ! n->analyze = false; ! n->full = $2; n->freeze_min_age = $3 ? 0 : -1; n->freeze_table_age = $3 ? 0 : -1; - n->verbose = $4; n->relation = NULL; n->va_cols = NIL; $$ = (Node *)n; --- 6626,6638 ---- VacuumStmt: VACUUM opt_full opt_freeze opt_verbose { VacuumStmt *n = makeNode(VacuumStmt); ! n->options = VACOPT_VACUUM; ! if ($2) ! n->options |= VACOPT_FULL; ! if ($4) ! n->options |= VACOPT_VERBOSE; n->freeze_min_age = $3 ? 0 : -1; n->freeze_table_age = $3 ? 0 : -1; n->relation = NULL; n->va_cols = NIL; $$ = (Node *)n; *************** VacuumStmt: VACUUM opt_full opt_freeze o *** 6638,6649 **** | VACUUM opt_full opt_freeze opt_verbose qualified_name { VacuumStmt *n = makeNode(VacuumStmt); ! n->vacuum = true; ! n->analyze = false; ! n->full = $2; n->freeze_min_age = $3 ? 0 : -1; n->freeze_table_age = $3 ? 0 : -1; - n->verbose = $4; n->relation = $5; n->va_cols = NIL; $$ = (Node *)n; --- 6640,6652 ---- | VACUUM opt_full opt_freeze opt_verbose qualified_name { VacuumStmt *n = makeNode(VacuumStmt); ! n->options = VACOPT_VACUUM; ! if ($2) ! n->options |= VACOPT_FULL; ! if ($4) ! n->options |= VACOPT_VERBOSE; n->freeze_min_age = $3 ? 0 : -1; n->freeze_table_age = $3 ? 0 : -1; n->relation = $5; n->va_cols = NIL; $$ = (Node *)n; *************** VacuumStmt: VACUUM opt_full opt_freeze o *** 6651,6675 **** | VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt { VacuumStmt *n = (VacuumStmt *) $5; ! n->vacuum = true; ! n->full = $2; n->freeze_min_age = $3 ? 0 : -1; n->freeze_table_age = $3 ? 0 : -1; - n->verbose |= $4; $$ = (Node *)n; } ; AnalyzeStmt: analyze_keyword opt_verbose { VacuumStmt *n = makeNode(VacuumStmt); ! n->vacuum = false; ! n->analyze = true; ! n->full = false; n->freeze_min_age = -1; n->freeze_table_age = -1; - n->verbose = $2; n->relation = NULL; n->va_cols = NIL; $$ = (Node *)n; --- 6654,6709 ---- | VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt { VacuumStmt *n = (VacuumStmt *) $5; ! n->options |= VACOPT_VACUUM; ! if ($2) ! n->options |= VACOPT_FULL; ! if ($4) ! n->options |= VACOPT_VERBOSE; n->freeze_min_age = $3 ? 0 : -1; n->freeze_table_age = $3 ? 0 : -1; $$ = (Node *)n; } + | VACUUM '(' vacuum_option_list ')' + { + VacuumStmt *n = makeNode(VacuumStmt); + n->options = VACOPT_VACUUM | $3; + n->relation = NULL; + n->va_cols = NIL; + $$ = (Node *) n; + } + | VACUUM '(' vacuum_option_list ')' qualified_name opt_name_list + { + VacuumStmt *n = makeNode(VacuumStmt); + n->options = VACOPT_VACUUM | $3; + n->relation = $5; + n->va_cols = $6; + $$ = (Node *) n; + } + ; + + vacuum_option_list: + vacuum_option_elem { $$ = $1; } + | vacuum_option_list ',' vacuum_option_elem { $$ = $1 | $3; } + ; + + vacuum_option_elem: + analyze_keyword { $$ = VACOPT_ANALYZE; } + | VERBOSE { $$ = VACOPT_VERBOSE; } + | FREEZE { $$ = VACOPT_FREEZE; } + | FULL { $$ = VACOPT_FULL; } + | FULL INPLACE { $$ = VACOPT_INPLACE; } + | FULL REPLACE { $$ = VACOPT_REPLACE; } ; AnalyzeStmt: analyze_keyword opt_verbose { VacuumStmt *n = makeNode(VacuumStmt); ! n->options = VACOPT_ANALYZE; ! if ($2) ! n->options |= VACOPT_VERBOSE; n->freeze_min_age = -1; n->freeze_table_age = -1; n->relation = NULL; n->va_cols = NIL; $$ = (Node *)n; *************** AnalyzeStmt: *** 6677,6688 **** | analyze_keyword opt_verbose qualified_name opt_name_list { VacuumStmt *n = makeNode(VacuumStmt); ! n->vacuum = false; ! n->analyze = true; ! n->full = false; n->freeze_min_age = -1; n->freeze_table_age = -1; - n->verbose = $2; n->relation = $3; n->va_cols = $4; $$ = (Node *)n; --- 6711,6721 ---- | analyze_keyword opt_verbose qualified_name opt_name_list { VacuumStmt *n = makeNode(VacuumStmt); ! n->options = VACOPT_ANALYZE; ! if ($2) ! n->options |= VACOPT_VERBOSE; n->freeze_min_age = -1; n->freeze_table_age = -1; n->relation = $3; n->va_cols = $4; $$ = (Node *)n; *************** unreserved_keyword: *** 10616,10621 **** --- 10649,10655 ---- | INHERIT | INHERITS | INLINE_P + | INPLACE | INPUT_P | INSENSITIVE | INSERT diff -cpr head/src/backend/postmaster/autovacuum.c work/src/backend/postmaster/autovacuum.c *** head/src/backend/postmaster/autovacuum.c 2009-09-01 04:40:59.000000000 +0900 --- work/src/backend/postmaster/autovacuum.c 2009-10-27 10:15:06.826375552 +0900 *************** autovacuum_do_vac_analyze(autovac_table *** 2640,2651 **** MemSet(&vacstmt, 0, sizeof(vacstmt)); vacstmt.type = T_VacuumStmt; ! vacstmt.vacuum = tab->at_dovacuum; ! vacstmt.full = false; ! vacstmt.analyze = tab->at_doanalyze; vacstmt.freeze_min_age = tab->at_freeze_min_age; vacstmt.freeze_table_age = tab->at_freeze_table_age; - vacstmt.verbose = false; vacstmt.relation = NULL; /* not used since we pass a relid */ vacstmt.va_cols = NIL; --- 2640,2651 ---- MemSet(&vacstmt, 0, sizeof(vacstmt)); vacstmt.type = T_VacuumStmt; ! if (tab->at_dovacuum) ! vacstmt.options |= VACOPT_VACUUM; ! if (tab->at_doanalyze) ! vacstmt.options |= VACOPT_ANALYZE; vacstmt.freeze_min_age = tab->at_freeze_min_age; vacstmt.freeze_table_age = tab->at_freeze_table_age; vacstmt.relation = NULL; /* not used since we pass a relid */ vacstmt.va_cols = NIL; diff -cpr head/src/backend/tcop/utility.c work/src/backend/tcop/utility.c *** head/src/backend/tcop/utility.c 2009-10-26 11:26:40.000000000 +0900 --- work/src/backend/tcop/utility.c 2009-10-27 10:51:53.562278044 +0900 *************** CreateCommandTag(Node *parsetree) *** 1815,1821 **** break; case T_VacuumStmt: ! if (((VacuumStmt *) parsetree)->vacuum) tag = "VACUUM"; else tag = "ANALYZE"; --- 1815,1821 ---- break; case T_VacuumStmt: ! if (((VacuumStmt *) parsetree)->options & VACOPT_VACUUM) tag = "VACUUM"; else tag = "ANALYZE"; diff -cpr head/src/include/commands/cluster.h work/src/include/commands/cluster.h *** head/src/include/commands/cluster.h 2009-01-02 02:23:58.000000000 +0900 --- work/src/include/commands/cluster.h 2009-10-27 11:26:14.373277502 +0900 *************** *** 18,24 **** extern void cluster(ClusterStmt *stmt, bool isTopLevel); ! extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck); extern void mark_index_clustered(Relation rel, Oid indexOid); --- 18,25 ---- 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 -cpr head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h *** head/src/include/nodes/parsenodes.h 2009-10-26 11:26:41.000000000 +0900 --- work/src/include/nodes/parsenodes.h 2009-10-27 13:23:10.700283894 +0900 *************** typedef struct ClusterStmt *** 2204,2216 **** * just one node type for both. * ---------------------- */ typedef struct VacuumStmt { NodeTag type; ! bool vacuum; /* do VACUUM step */ ! bool full; /* do FULL (non-concurrent) vacuum */ ! bool analyze; /* do ANALYZE step */ ! bool verbose; /* print progress info */ int freeze_min_age; /* min freeze age, or -1 to use default */ int freeze_table_age; /* age at which to scan whole table */ RangeVar *relation; /* single table to process, or NULL */ --- 2204,2226 ---- * just one node type for both. * ---------------------- */ + + typedef enum VacuumOption + { + VACOPT_VACUUM = 1 << 0, /* do VACUUM */ + VACOPT_ANALYZE = 1 << 1, /* do ANALYZE */ + VACOPT_VERBOSE = 1 << 2, /* VERBOSE option */ + VACOPT_FREEZE = 1 << 3, /* FREEZE option */ + VACOPT_INPLACE = 1 << 4, /* traditional VACUUM FULL */ + VACOPT_REPLACE = 1 << 5, /* rewrite-version of VACUUM FULL */ + + VACOPT_FULL = VACOPT_REPLACE /* default is REPLACE */ + } VacuumOption; + typedef struct VacuumStmt { NodeTag type; ! bits32 options; /* OR of VacuumOption flags */ int freeze_min_age; /* min freeze age, or -1 to use default */ int freeze_table_age; /* age at which to scan whole table */ RangeVar *relation; /* single table to process, or NULL */ diff -cpr head/src/include/parser/kwlist.h work/src/include/parser/kwlist.h *** head/src/include/parser/kwlist.h 2009-10-13 05:39:42.000000000 +0900 --- work/src/include/parser/kwlist.h 2009-10-27 13:16:20.770282670 +0900 *************** PG_KEYWORD("initially", INITIALLY, RESER *** 192,197 **** --- 192,198 ---- 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)