diff -cprN head/doc/src/sgml/ref/vacuum.sgml work/doc/src/sgml/ref/vacuum.sgml
*** head/doc/src/sgml/ref/vacuum.sgml	2009-11-24 10:04:57.883822000 +0900
--- work/doc/src/sgml/ref/vacuum.sgml	2009-12-15 17:12:00.286244057 +0900
*************** PostgreSQL documentation
*** 21,27 ****
  
   <refsynopsisdiv>
  <synopsis>
! VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE } [, ...] ) ] [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
  VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> ]
  VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
  </synopsis>
--- 21,27 ----
  
   <refsynopsisdiv>
  <synopsis>
! VACUUM [ ( { FULL [ INPLACE ] | FREEZE | VERBOSE | ANALYZE } [, ...] ) ] [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
  VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> ]
  VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
  </synopsis>
*************** VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] A
*** 86,91 ****
--- 86,112 ----
        Selects <quote>full</quote> vacuum, which can reclaim more
        space, but takes much longer and exclusively locks the table.
       </para>
+      <para>
+       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.
+      </para>
+      <para>
+       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 <command>VACUUM FULL</command> on a
+       comparable user table, but the indexes will grow which may
+       counteract that benefit. Additionally, the operation is often
+       slower than <command>VACUUM FULL</command> on a comparable user
+       table.
+      </para>
+      <para>
+       If <literal>FULL INPLACE</literal> is specified, the space is
+       reclaimed in the same manner as a system table, even if it is a
+       user table. Specifying <literal>INPLACE</literal> explicitly is
+       rarely useful.
+      </para>
      </listitem>
     </varlistentry>
  
diff -cprN head/doc/src/sgml/ref/vacuumdb.sgml work/doc/src/sgml/ref/vacuumdb.sgml
*** head/doc/src/sgml/ref/vacuumdb.sgml	2009-11-30 11:53:27.209342000 +0900
--- work/doc/src/sgml/ref/vacuumdb.sgml	2009-12-15 17:42:17.543360261 +0900
*************** PostgreSQL documentation
*** 24,29 ****
--- 24,30 ----
     <command>vacuumdb</command>
     <arg rep="repeat"><replaceable>connection-option</replaceable></arg>
     <group><arg>--full</arg><arg>-f</arg></group>
+    <group><arg>--inplace</arg><arg>-i</arg></group>
     <group><arg>--verbose</arg><arg>-v</arg></group>
     <group><arg>--analyze</arg><arg>-z</arg></group>
     <group><arg>--freeze</arg><arg>-F</arg></group>
*************** PostgreSQL documentation
*** 36,41 ****
--- 37,43 ----
     <arg rep="repeat"><replaceable>connection-options</replaceable></arg>
     <group><arg>--all</arg><arg>-a</arg></group>
     <group><arg>--full</arg><arg>-f</arg></group>
+    <group><arg>--inplace</arg><arg>-i</arg></group>
     <group><arg>--verbose</arg><arg>-v</arg></group>
     <group><arg>--analyze</arg><arg>-z</arg></group>
     <group><arg>--freeze</arg><arg>-F</arg></group>
*************** PostgreSQL documentation
*** 117,122 ****
--- 119,134 ----
       </varlistentry>
  
       <varlistentry>
+       <term><option>-i</option></term>
+       <term><option>--inplace</option></term>
+       <listitem>
+        <para>
+         Perform <quote>full inplace</quote> vacuuming.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
        <term><option>-q</></term>
        <term><option>--quiet</></term>
        <listitem>
diff -cprN head/src/backend/commands/cluster.c work/src/backend/commands/cluster.c
*** head/src/backend/commands/cluster.c	2009-10-06 16:37:35.607642000 +0900
--- work/src/backend/commands/cluster.c	2009-12-15 17:49:02.738051644 +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,366 ----
  			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 */
! 	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 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 ****
--- 372,409 ----
  {
  	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);
  }
--- 486,491 ----
*************** 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;
--- 575,582 ----
   * 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);
--- 587,594 ----
  	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();
--- 611,618 ----
  	/*
  	 * 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,
--- 771,778 ----
   * 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;
--- 782,789 ----
  	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
--- 794,803 ----
  	 */
  	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
--- 826,833 ----
  	 * 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 */
--- 845,890 ----
  	 * 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)
  		{
--- 926,932 ----
  				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);
--- 970,979 ----
  		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);
  
--- 981,988 ----
  	pfree(values);
  	pfree(isnull);
  
! 	if (OldIndex != NULL)
! 		index_close(OldIndex, NoLock);
  	heap_close(OldHeap, NoLock);
  	heap_close(NewHeap, NoLock);
  
diff -cprN head/src/backend/commands/vacuum.c work/src/backend/commands/vacuum.c
*** head/src/backend/commands/vacuum.c	2009-12-10 09:19:59.669917000 +0900
--- work/src/backend/commands/vacuum.c	2009-12-15 18:03:03.053054721 +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"
*************** vacuum(VacuumStmt *vacstmt, Oid relid, b
*** 302,307 ****
--- 304,311 ----
  	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";
  
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1204,1215 ****
  	save_nestlevel = NewGUCNestLevel();
  
  	/*
! 	 * Do the actual work --- either FULL or "lazy" vacuum
  	 */
! 	if (vacstmt->options & VACOPT_FULL)
  		heldoff = full_vacuum_rel(onerel, vacstmt);
  	else
! 		heldoff = lazy_vacuum_rel(onerel, vacstmt, vac_strategy, scanned_all);
  
  	/* Roll back any GUC changes executed by index functions */
  	AtEOXact_GUC(false, save_nestlevel);
--- 1208,1231 ----
  	save_nestlevel = NewGUCNestLevel();
  
  	/*
! 	 * Do the actual work --- either FULL, FULL INPLACE, or "lazy" vacuum.
! 	 * We can only use FULL INPLACE vacuum for system relations.
  	 */
! 	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
! 	{
! 		/* 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);
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1218,1224 ****
  	SetUserIdAndSecContext(save_userid, save_sec_context);
  
  	/* all done with this class, but hold lock until commit */
! 	relation_close(onerel, NoLock);
  
  	/*
  	 * Complete the transaction and free all temporary memory used.
--- 1234,1241 ----
  	SetUserIdAndSecContext(save_userid, save_sec_context);
  
  	/* 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.
diff -cprN head/src/backend/parser/gram.y work/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-12-11 12:39:49.829461000 +0900
--- work/src/backend/parser/gram.y	2009-12-15 17:12:00.294048384 +0900
*************** 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
--- 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 INPLACE INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
  	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
  
  	JOIN
*************** vacuum_option_elem:
*** 6773,6778 ****
--- 6773,6779 ----
  			| VERBOSE			{ $$ = VACOPT_VERBOSE; }
  			| FREEZE			{ $$ = VACOPT_FREEZE; }
  			| FULL				{ $$ = VACOPT_FULL; }
+ 			| FULL INPLACE		{ $$ = VACOPT_FULL | VACOPT_INPLACE; }
  		;
  
  AnalyzeStmt:
*************** unreserved_keyword:
*** 10719,10724 ****
--- 10720,10726 ----
  			| INHERIT
  			| INHERITS
  			| INLINE_P
+ 			| INPLACE
  			| INPUT_P
  			| INSENSITIVE
  			| INSERT
diff -cprN head/src/bin/scripts/vacuumdb.c work/src/bin/scripts/vacuumdb.c
*** head/src/bin/scripts/vacuumdb.c	2009-10-20 14:30:53.614193000 +0900
--- work/src/bin/scripts/vacuumdb.c	2009-12-15 17:12:00.294048384 +0900
***************
*** 14,28 ****
  #include "common.h"
  
  
! static void vacuum_one_database(const char *dbname, bool full, bool verbose, bool 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 analyze, bool freeze,
! 					 const char *host, const char *port,
! 					 const char *username, enum trivalue prompt_password,
! 					 const char *progname, bool echo, bool quiet);
  
  static void help(const char *progname);
  
--- 14,29 ----
  #include "common.h"
  
  
! static void vacuum_one_database(const char *dbname, bool full, bool inplace,
! 					bool verbose, bool 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 inplace,
! 					bool verbose, bool analyze, bool freeze,
! 					const char *host, const char *port,
! 					const char *username, enum trivalue prompt_password,
! 					const char *progname, bool echo, bool quiet);
  
  static void help(const char *progname);
  
*************** main(int argc, char *argv[])
*** 45,50 ****
--- 46,52 ----
  		{"table", required_argument, NULL, 't'},
  		{"full", no_argument, NULL, 'f'},
  		{"verbose", no_argument, NULL, 'v'},
+ 		{"inplace", no_argument, NULL, 'i'},
  		{NULL, 0, NULL, 0}
  	};
  
*************** main(int argc, char *argv[])
*** 65,77 ****
  	char	   *table = NULL;
  	bool		full = false;
  	bool		verbose = 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)
  	{
  		switch (c)
  		{
--- 67,80 ----
  	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:fiv", long_options, &optindex)) != -1)
  	{
  		switch (c)
  		{
*************** main(int argc, char *argv[])
*** 117,122 ****
--- 120,128 ----
  			case 'v':
  				verbose = true;
  				break;
+ 			case 'i':
+ 				inplace = true;
+ 				break;
  			default:
  				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
  				exit(1);
*************** main(int argc, char *argv[])
*** 137,142 ****
--- 143,156 ----
  			exit(1);
  	}
  
+ 	/* INPLACE option requires FULL option. */
+ 	if (inplace && !full)
+ 	{
+ 		fprintf(stderr, _("%s: -i, --inplace option requires -f, --full\n"),
+ 				progname);
+ 		exit(1);
+ 	}
+ 
  	setup_cancel_handler();
  
  	if (alldb)
*************** main(int argc, char *argv[])
*** 154,160 ****
  			exit(1);
  		}
  
! 		vacuum_all_databases(full, verbose, analyze, freeze,
  							 host, port, username, prompt_password,
  							 progname, echo, quiet);
  	}
--- 168,174 ----
  			exit(1);
  		}
  
! 		vacuum_all_databases(full, inplace, verbose, analyze, freeze,
  							 host, port, username, prompt_password,
  							 progname, echo, quiet);
  	}
*************** main(int argc, char *argv[])
*** 170,177 ****
  				dbname = get_user_name(progname);
  		}
  
! 		vacuum_one_database(dbname, full, verbose, analyze, freeze, table,
! 							host, port, username, prompt_password,
  							progname, echo);
  	}
  
--- 184,191 ----
  				dbname = get_user_name(progname);
  		}
  
! 		vacuum_one_database(dbname, full, inplace, verbose, analyze, freeze,
! 							table, host, port, username, prompt_password,
  							progname, echo);
  	}
  
*************** main(int argc, char *argv[])
*** 180,187 ****
  
  
  static void
! vacuum_one_database(const char *dbname, bool full, bool verbose, bool analyze,
! 					bool freeze, const char *table,
  					const char *host, const char *port,
  					const char *username, enum trivalue prompt_password,
  					const char *progname, bool echo)
--- 194,201 ----
  
  
  static void
! vacuum_one_database(const char *dbname, bool full, bool inplace, bool verbose,
! 					bool analyze, bool freeze, const char *table,
  					const char *host, const char *port,
  					const char *username, enum trivalue prompt_password,
  					const char *progname, bool echo)
*************** vacuum_one_database(const char *dbname, 
*** 189,211 ****
  	PQExpBufferData sql;
  
  	PGconn	   *conn;
  
  	initPQExpBuffer(&sql);
  
  	appendPQExpBuffer(&sql, "VACUUM");
! 	if (full)
! 		appendPQExpBuffer(&sql, " FULL");
! 	if (freeze)
! 		appendPQExpBuffer(&sql, " FREEZE");
! 	if (verbose)
! 		appendPQExpBuffer(&sql, " VERBOSE");
! 	if (analyze)
! 		appendPQExpBuffer(&sql, " ANALYZE");
  	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)
--- 203,264 ----
  	PQExpBufferData sql;
  
  	PGconn	   *conn;
+ 	int			version;
+ 	bool		first_opt = true;
  
  	initPQExpBuffer(&sql);
  
+ 	conn = connectDatabase(dbname, host, port, username, prompt_password, progname);
+ 	version = PQserverVersion(conn);
+ 
  	appendPQExpBuffer(&sql, "VACUUM");
! 
! 	if (version >= 80500)
! 	{
! 		if (full)
! 		{
! 			appendPQExpBuffer(&sql, "%sFULL%s", first_opt ? " (" : ", ",
! 							  inplace ? " INPLACE" : "");
! 			first_opt = false;
! 		}
! 		if (freeze)
! 		{
! 			appendPQExpBuffer(&sql, "%sFREEZE", first_opt ? " (" : ", ");
! 			first_opt = false;
! 		}
! 		if (verbose)
! 		{
! 			appendPQExpBuffer(&sql, "%sVERBOSE", first_opt ? " (" : ", ");
! 			first_opt = false;
! 		}
! 		if (analyze)
! 		{
! 			appendPQExpBuffer(&sql, "%sANALYZE", first_opt ? " (" : ", ");
! 			first_opt = false;
! 		}
! 		if (!first_opt)
! 			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 (analyze)
! 			appendPQExpBuffer(&sql, " ANALYZE");
! 	}
! 
  	if (table)
  		appendPQExpBuffer(&sql, " %s", table);
  	appendPQExpBuffer(&sql, ";\n");
  
  	if (!executeMaintenanceCommand(conn, sql.data, echo))
  	{
  		if (table)
*************** vacuum_one_database(const char *dbname, 
*** 223,230 ****
  
  
  static void
! vacuum_all_databases(bool full, bool verbose, bool analyze, bool freeze,
! 					 const char *host, const char *port,
  					 const char *username, enum trivalue prompt_password,
  					 const char *progname, bool echo, bool quiet)
  {
--- 276,283 ----
  
  
  static void
! vacuum_all_databases(bool full, bool inplace, bool verbose, bool analyze,
! 					 bool freeze, const char *host, const char *port,
  					 const char *username, enum trivalue prompt_password,
  					 const char *progname, bool echo, bool quiet)
  {
*************** vacuum_all_databases(bool full, bool ver
*** 246,253 ****
  			fflush(stdout);
  		}
  
! 		vacuum_one_database(dbname, full, verbose, analyze, freeze, NULL,
! 							host, port, username, prompt_password,
  							progname, echo);
  	}
  
--- 299,306 ----
  			fflush(stdout);
  		}
  
! 		vacuum_one_database(dbname, full, inplace, verbose, analyze, freeze,
! 							NULL, host, port, username, prompt_password,
  							progname, echo);
  	}
  
*************** help(const char *progname)
*** 267,272 ****
--- 320,326 ----
  	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(_("  -q, --quiet                     don't write any messages\n"));
  	printf(_("  -t, --table='TABLE[(COLUMNS)]'  vacuum specific table only\n"));
  	printf(_("  -v, --verbose                   write a lot of output\n"));
diff -cprN head/src/include/commands/cluster.h work/src/include/commands/cluster.h
*** head/src/include/commands/cluster.h	2009-01-05 00:22:25.168790000 +0900
--- work/src/include/commands/cluster.h	2009-12-15 17:12:00.295055317 +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 -cprN head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-12-11 12:39:49.829461000 +0900
--- work/src/include/nodes/parsenodes.h	2009-12-15 17:12:00.296047000 +0900
*************** typedef enum VacuumOption
*** 2233,2239 ****
  	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 */
  } VacuumOption;
  
  typedef struct VacuumStmt
--- 2233,2240 ----
  	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_INPLACE		= 1 << 5	/* traditional FULL INPLACE vacuum */
  } VacuumOption;
  
  typedef struct VacuumStmt
diff -cprN head/src/include/parser/kwlist.h work/src/include/parser/kwlist.h
*** head/src/include/parser/kwlist.h	2009-12-09 13:45:22.745455000 +0900
--- work/src/include/parser/kwlist.h	2009-12-15 17:12:00.296047000 +0900
*************** PG_KEYWORD("initially", INITIALLY, RESER
*** 193,198 ****
--- 193,199 ----
  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 -cprN head/src/test/regress/expected/vacuum.out work/src/test/regress/expected/vacuum.out
*** head/src/test/regress/expected/vacuum.out	2009-11-24 10:04:57.883822000 +0900
--- work/src/test/regress/expected/vacuum.out	2009-12-15 17:12:00.297048245 +0900
*************** SELECT * FROM vactst;
*** 57,60 ****
--- 57,121 ----
  (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 -cprN head/src/test/regress/sql/vacuum.sql work/src/test/regress/sql/vacuum.sql
*** head/src/test/regress/sql/vacuum.sql	2009-11-24 10:04:57.883822000 +0900
--- work/src/test/regress/sql/vacuum.sql	2009-12-15 17:12:00.297048245 +0900
*************** DELETE FROM vactst;
*** 40,44 ****
--- 40,101 ----
  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;
