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 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/vacuum.sgml,v 1.56 2009/11/16 21:32:06 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/vacuum.sgml,v 1.57 2010/01/06 05:31:13 itagaki Exp $
 PostgreSQL documentation
 -->
 
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE } [, ...] ) ] [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
+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>
@@ -86,6 +86,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
       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 --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 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/vacuumdb.sgml,v 1.46 2010/01/06 02:59:44 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/vacuumdb.sgml,v 1.47 2010/01/06 05:31:13 itagaki Exp $
 PostgreSQL documentation
 -->
 
@@ -24,6 +24,7 @@ PostgreSQL documentation
    <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>--freeze</arg><arg>-F</arg></group>
    <group><arg>--verbose</arg><arg>-v</arg></group>
    <group><arg>--analyze</arg><arg>-z</arg></group>
@@ -37,6 +38,7 @@ PostgreSQL documentation
    <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>--freeze</arg><arg>-F</arg></group>
    <group><arg>--verbose</arg><arg>-v</arg></group>
    <group><arg>--analyze</arg><arg>-z</arg></group>
@@ -129,6 +131,16 @@ PostgreSQL documentation
       </listitem>
      </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>-o</option></term>
       <term><option>--only-analyze</option></term>
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;