diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml
index 01381ff7058049d15c59d8ef0788323de7833dcc..61956cdfcf5d7386cef83c7b17c834d39ebf2be7 100644
--- a/doc/src/sgml/indexam.sgml
+++ b/doc/src/sgml/indexam.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/indexam.sgml,v 2.10 2006/05/02 22:25:09 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/indexam.sgml,v 2.11 2006/05/10 23:18:38 tgl Exp $ -->
 
 <chapter id="indexam">
  <title>Index Access Method Interface Definition</title>
@@ -144,7 +144,7 @@
 
   <para>
 <programlisting>
-void
+IndexBuildResult *
 ambuild (Relation heapRelation,
          Relation indexRelation,
          IndexInfo *indexInfo);
@@ -155,6 +155,8 @@ ambuild (Relation heapRelation,
    in the table.  Ordinarily the <function>ambuild</> function will call
    <function>IndexBuildHeapScan()</> to scan the table for existing tuples
    and compute the keys that need to be inserted into the index.
+   The function must return a palloc'd struct containing statistics about
+   the new index.
   </para>
 
   <para>
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index fd7fc9f823bfe067c10d58c88ae4d30c8ef3d98c..a4416a94cb3348d970ef10ed90f4c1a28b86d481 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *          $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.1 2006/05/02 11:28:54 teodor Exp $
+ *          $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.2 2006/05/10 23:18:38 tgl Exp $
  *-------------------------------------------------------------------------
  */
 
@@ -242,6 +242,7 @@ ginbuild(PG_FUNCTION_ARGS) {
 	Relation    heap = (Relation) PG_GETARG_POINTER(0);
 	Relation    index = (Relation) PG_GETARG_POINTER(1);
 	IndexInfo  *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
+	IndexBuildResult *result;
 	double      reltuples;
 	GinBuildState	buildstate;
 	Buffer		buffer;
@@ -310,10 +311,15 @@ ginbuild(PG_FUNCTION_ARGS) {
 
 	MemoryContextDelete(buildstate.tmpCtx);
 
-	/* since we just counted the # of tuples, may as well update stats */
-	IndexCloseAndUpdateStats(heap, reltuples, index, buildstate.indtuples);
+	/*
+	 * Return statistics
+	 */
+	result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
+
+	result->heap_tuples = reltuples;
+	result->index_tuples = buildstate.indtuples;
 
-	PG_RETURN_VOID();
+	PG_RETURN_POINTER(result);
 }
 
 /*
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 2272e3339d14d24e50742dab7cf3d8423b0e3d12..4ce461d4463ba739439d6b2eaafd3a02d8f4c1a8 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/gist/gist.c,v 1.133 2006/05/10 09:19:54 teodor Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/gist/gist.c,v 1.134 2006/05/10 23:18:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -89,6 +89,7 @@ gistbuild(PG_FUNCTION_ARGS)
 	Relation	heap = (Relation) PG_GETARG_POINTER(0);
 	Relation	index = (Relation) PG_GETARG_POINTER(1);
 	IndexInfo  *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
+	IndexBuildResult *result;
 	double		reltuples;
 	GISTBuildState buildstate;
 	Buffer		buffer;
@@ -154,12 +155,17 @@ gistbuild(PG_FUNCTION_ARGS)
 	/* okay, all heap tuples are indexed */
 	MemoryContextDelete(buildstate.tmpCtx);
 
-	/* since we just counted the # of tuples, may as well update stats */
-	IndexCloseAndUpdateStats(heap, reltuples, index, buildstate.indtuples);
-
 	freeGISTstate(&buildstate.giststate);
 
-	PG_RETURN_VOID();
+	/*
+	 * Return statistics
+	 */
+	result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
+
+	result->heap_tuples = reltuples;
+	result->index_tuples = buildstate.indtuples;
+
+	PG_RETURN_POINTER(result);
 }
 
 /*
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index ff54052f6de28e2f586103d6567c7a1126020adf..d94104854e97cc8ed80f9a7dded8f0a22479ff09 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.89 2006/05/02 22:25:10 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.90 2006/05/10 23:18:38 tgl Exp $
  *
  * NOTES
  *	  This file contains only the public interface routines.
@@ -51,6 +51,7 @@ hashbuild(PG_FUNCTION_ARGS)
 	Relation	heap = (Relation) PG_GETARG_POINTER(0);
 	Relation	index = (Relation) PG_GETARG_POINTER(1);
 	IndexInfo  *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
+	IndexBuildResult *result;
 	double		reltuples;
 	HashBuildState buildstate;
 
@@ -72,10 +73,15 @@ hashbuild(PG_FUNCTION_ARGS)
 	reltuples = IndexBuildHeapScan(heap, index, indexInfo,
 								   hashbuildCallback, (void *) &buildstate);
 
-	/* since we just counted the # of tuples, may as well update stats */
-	IndexCloseAndUpdateStats(heap, reltuples, index, buildstate.indtuples);
+	/*
+	 * Return statistics
+	 */
+	result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
 
-	PG_RETURN_VOID();
+	result->heap_tuples = reltuples;
+	result->index_tuples = buildstate.indtuples;
+
+	PG_RETURN_POINTER(result);
 }
 
 /*
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 779e86a38940ba4619d856a1f034e88c2708f889..dcb9fc8fb64dca094b7345fde5f1fbfeacb7d40e 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.211 2006/03/31 23:32:05 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.212 2006/05/10 23:18:39 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -2673,6 +2673,97 @@ l3:
 	return HeapTupleMayBeUpdated;
 }
 
+
+/*
+ * heap_inplace_update - update a tuple "in place" (ie, overwrite it)
+ *
+ * Overwriting violates both MVCC and transactional safety, so the uses
+ * of this function in Postgres are extremely limited.  Nonetheless we
+ * find some places to use it.
+ *
+ * The tuple cannot change size, and therefore it's reasonable to assume
+ * that its null bitmap (if any) doesn't change either.  So we just
+ * overwrite the data portion of the tuple without touching the null
+ * bitmap or any of the header fields.
+ *
+ * tuple is an in-memory tuple structure containing the data to be written
+ * over the target tuple.  Also, tuple->t_self identifies the target tuple.
+ */
+void
+heap_inplace_update(Relation relation, HeapTuple tuple)
+{
+	Buffer		buffer;
+	Page		page;
+	OffsetNumber offnum;
+	ItemId		lp = NULL;
+	HeapTupleHeader htup;
+	uint32		oldlen;
+	uint32		newlen;
+
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+	page = (Page) BufferGetPage(buffer);
+
+	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
+	if (PageGetMaxOffsetNumber(page) >= offnum)
+		lp = PageGetItemId(page, offnum);
+
+	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsUsed(lp))
+		elog(ERROR, "heap_inplace_update: invalid lp");
+
+	htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+	oldlen = ItemIdGetLength(lp) - htup->t_hoff;
+	newlen = tuple->t_len - tuple->t_data->t_hoff;
+	if (oldlen != newlen || htup->t_hoff != tuple->t_data->t_hoff)
+		elog(ERROR, "heap_inplace_update: wrong tuple length");
+
+	/* NO EREPORT(ERROR) from here till changes are logged */
+	START_CRIT_SECTION();
+
+	memcpy((char *) htup + htup->t_hoff,
+		   (char *) tuple->t_data + tuple->t_data->t_hoff,
+		   newlen);
+
+	MarkBufferDirty(buffer);
+
+	/* XLOG stuff */
+	if (!relation->rd_istemp)
+	{
+		xl_heap_inplace xlrec;
+		XLogRecPtr	recptr;
+		XLogRecData rdata[2];
+
+		xlrec.target.node = relation->rd_node;
+		xlrec.target.tid = tuple->t_self;
+
+		rdata[0].data = (char *) &xlrec;
+		rdata[0].len = SizeOfHeapInplace;
+		rdata[0].buffer = InvalidBuffer;
+		rdata[0].next = &(rdata[1]);
+
+		rdata[1].data = (char *) htup + htup->t_hoff;
+		rdata[1].len = newlen;
+		rdata[1].buffer = buffer;
+		rdata[1].buffer_std = true;
+		rdata[1].next = NULL;
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_INPLACE, rdata);
+
+		PageSetLSN(page, recptr);
+		PageSetTLI(page, ThisTimeLineID);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buffer);
+
+	/* Send out shared cache inval if necessary */
+	if (!IsBootstrapProcessingMode())
+		CacheInvalidateHeapTuple(relation, tuple);
+}
+
+
 /* ----------------
  *		heap_markpos	- mark scan position
  * ----------------
@@ -3329,6 +3420,59 @@ heap_xlog_lock(XLogRecPtr lsn, XLogRecord *record)
 	UnlockReleaseBuffer(buffer);
 }
 
+static void
+heap_xlog_inplace(XLogRecPtr lsn, XLogRecord *record)
+{
+	xl_heap_inplace *xlrec = (xl_heap_inplace *) XLogRecGetData(record);
+	Relation	reln = XLogOpenRelation(xlrec->target.node);
+	Buffer		buffer;
+	Page		page;
+	OffsetNumber offnum;
+	ItemId		lp = NULL;
+	HeapTupleHeader htup;
+	uint32		oldlen;
+	uint32		newlen;
+
+	if (record->xl_info & XLR_BKP_BLOCK_1)
+		return;
+
+	buffer = XLogReadBuffer(reln,
+							ItemPointerGetBlockNumber(&(xlrec->target.tid)),
+							false);
+	if (!BufferIsValid(buffer))
+		return;
+	page = (Page) BufferGetPage(buffer);
+
+	if (XLByteLE(lsn, PageGetLSN(page)))		/* changes are applied */
+	{
+		UnlockReleaseBuffer(buffer);
+		return;
+	}
+
+	offnum = ItemPointerGetOffsetNumber(&(xlrec->target.tid));
+	if (PageGetMaxOffsetNumber(page) >= offnum)
+		lp = PageGetItemId(page, offnum);
+
+	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsUsed(lp))
+		elog(PANIC, "heap_inplace_redo: invalid lp");
+
+	htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+	oldlen = ItemIdGetLength(lp) - htup->t_hoff;
+	newlen = record->xl_len - SizeOfHeapInplace;
+	if (oldlen != newlen)
+		elog(PANIC, "heap_inplace_redo: wrong tuple length");
+
+	memcpy((char *) htup + htup->t_hoff,
+		   (char *) xlrec + SizeOfHeapInplace,
+		   newlen);
+
+	PageSetLSN(page, lsn);
+	PageSetTLI(page, ThisTimeLineID);
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+}
+
 void
 heap_redo(XLogRecPtr lsn, XLogRecord *record)
 {
@@ -3349,6 +3493,8 @@ heap_redo(XLogRecPtr lsn, XLogRecord *record)
 		heap_xlog_newpage(lsn, record);
 	else if (info == XLOG_HEAP_LOCK)
 		heap_xlog_lock(lsn, record);
+	else if (info == XLOG_HEAP_INPLACE)
+		heap_xlog_inplace(lsn, record);
 	else
 		elog(PANIC, "heap_redo: unknown op code %u", info);
 }
@@ -3442,6 +3588,13 @@ heap_desc(StringInfo buf, uint8 xl_info, char *rec)
 		appendStringInfo(buf, "%u ", xlrec->locking_xid);
 		out_target(buf, &(xlrec->target));
 	}
+	else if (info == XLOG_HEAP_INPLACE)
+	{
+		xl_heap_inplace *xlrec = (xl_heap_inplace *) rec;
+
+		appendStringInfo(buf, "inplace: ");
+		out_target(buf, &(xlrec->target));
+	}
 	else
 		appendStringInfo(buf, "UNKNOWN");
 }
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index d501249a7a602fc1aabf2f86cbfa61221513b00d..3329321c0ffd14727301296d441ace0dd1ad4bce 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -12,21 +12,18 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.148 2006/05/08 00:00:10 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.149 2006/05/10 23:18:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "access/genam.h"
-#include "access/heapam.h"
 #include "access/nbtree.h"
 #include "catalog/index.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
 #include "storage/freespace.h"
-#include "storage/smgr.h"
-#include "utils/inval.h"
+#include "storage/lmgr.h"
 #include "utils/memutils.h"
 
 
@@ -84,6 +81,7 @@ btbuild(PG_FUNCTION_ARGS)
 	Relation	heap = (Relation) PG_GETARG_POINTER(0);
 	Relation	index = (Relation) PG_GETARG_POINTER(1);
 	IndexInfo  *indexInfo = (IndexInfo *) PG_GETARG_POINTER(2);
+	IndexBuildResult *result;
 	double		reltuples;
 	BTBuildState buildstate;
 
@@ -149,18 +147,20 @@ btbuild(PG_FUNCTION_ARGS)
 	/*
 	 * If we are reindexing a pre-existing index, it is critical to send out
 	 * a relcache invalidation SI message to ensure all backends re-read the
-	 * index metapage.  In most circumstances the update-stats operation will
-	 * cause that to happen, but at the moment there are corner cases where
-	 * no pg_class update will occur, so force an inval here.  XXX FIXME:
-	 * the upper levels of CREATE INDEX should handle the stats update as
-	 * well as guaranteeing relcache inval.
+	 * index metapage.  We expect that the caller will ensure that happens
+	 * (typically as a side effect of updating index stats, but it must
+	 * happen even if the stats don't change!)
 	 */
-	CacheInvalidateRelcache(index);
 
-	/* since we just counted the # of tuples, may as well update stats */
-	IndexCloseAndUpdateStats(heap, reltuples, index, buildstate.indtuples);
+	/*
+	 * Return statistics
+	 */
+	result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
 
-	PG_RETURN_VOID();
+	result->heap_tuples = reltuples;
+	result->index_tuples = buildstate.indtuples;
+
+	PG_RETURN_POINTER(result);
 }
 
 /*
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index c1d16b0e8fccd81bf4d8b93921b8cc6aba734774..5dfda5a427d48de7835286ecb5337299b578543c 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.214 2006/04/04 19:35:33 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.215 2006/05/10 23:18:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1173,11 +1173,12 @@ AddStr(char *str, int strlength, int mderef)
  *	index_register() -- record an index that has been set up for building
  *						later.
  *
- *		At bootstrap time, we define a bunch of indices on system catalogs.
- *		We postpone actually building the indices until just before we're
- *		finished with initialization, however.	This is because more classes
- *		and indices may be defined, and we want to be sure that all of them
- *		are present in the index.
+ *		At bootstrap time, we define a bunch of indexes on system catalogs.
+ *		We postpone actually building the indexes until just before we're
+ *		finished with initialization, however.	This is because the indexes
+ *		themselves have catalog entries, and those have to be included in the
+ *		indexes on those catalogs.  Doing it in two phases is the simplest
+ *		way of making sure the indexes have the right contents at the end.
  */
 void
 index_register(Oid heap,
@@ -1189,7 +1190,7 @@ index_register(Oid heap,
 
 	/*
 	 * XXX mao 10/31/92 -- don't gc index reldescs, associated info at
-	 * bootstrap time.	we'll declare the indices now, but want to create them
+	 * bootstrap time.	we'll declare the indexes now, but want to create them
 	 * later.
 	 */
 
@@ -1223,6 +1224,10 @@ index_register(Oid heap,
 	MemoryContextSwitchTo(oldcxt);
 }
 
+
+/*
+ * build_indices -- fill in all the indexes registered earlier
+ */
 void
 build_indices(void)
 {
@@ -1233,13 +1238,10 @@ build_indices(void)
 
 		heap = heap_open(ILHead->il_heap, NoLock);
 		ind = index_open(ILHead->il_ind);
-		index_build(heap, ind, ILHead->il_info);
 
-		/*
-		 * In normal processing mode, index_build would close the heap and
-		 * index, but in bootstrap mode it will not.
-		 */
+		index_build(heap, ind, ILHead->il_info, false, false);
 
-		/* XXX Probably we ought to close the heap and index here? */
+		index_close(ind);
+		heap_close(heap, NoLock);
 	}
 }
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e4514c37ea24b0bbc42a4891c83e92e80339ea59..a3e629307452ab6fff7b49f5436e8c86d69b5a72 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.298 2006/04/30 01:08:06 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.299 2006/05/10 23:18:39 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -1921,17 +1921,14 @@ RelationTruncateIndexes(Oid heapId)
 		RelationTruncate(currentIndex, 0);
 
 		/* Initialize the index and rebuild */
-		index_build(heapRelation, currentIndex, indexInfo);
+		/* Note: we do not need to re-establish pkey or toast settings */
+		index_build(heapRelation, currentIndex, indexInfo, false, false);
 
-		/*
-		 * index_build will close both the heap and index relations (but not
-		 * give up the locks we hold on them).	We're done with this index,
-		 * but we must re-open the heap rel.
-		 */
-		heapRelation = heap_open(heapId, NoLock);
+		/* We're done with this index */
+		index_close(currentIndex);
 	}
 
-	/* Finish by closing the heap rel again */
+	/* And now done with the heap; but keep lock until commit */
 	heap_close(heapRelation, NoLock);
 }
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 75302edfafddda4cd06beb4ddef73a2c2996575b..d0f216475ea5cbed9f127284274782741c49a1c3 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.265 2006/03/31 23:32:06 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.266 2006/05/10 23:18:39 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -61,8 +61,9 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 					IndexInfo *indexInfo,
 					Oid *classOids,
 					bool primary);
+static void index_update_stats(Relation rel, bool hasindex, bool isprimary,
+				   Oid reltoastidxid, double reltuples);
 static Oid	IndexGetRelation(Oid indexId);
-static void UpdateStats(Oid relid, double reltuples);
 
 
 /*
@@ -437,15 +438,26 @@ UpdateIndexRelation(Oid indexoid,
 }
 
 
-/* ----------------------------------------------------------------
- *		index_create
+/*
+ * index_create
  *
- *		indexRelationId is normally InvalidOid to let this routine
- *		generate an OID for the index.	During bootstrap it may be
+ * heapRelationId: OID of table to build index on
+ * indexRelationName: what it say
+ * indexRelationId: normally, pass InvalidOid to let this routine
+ *		generate an OID for the index.	During bootstrap this may be
  *		nonzero to specify a preselected OID.
+ * indexInfo: same info executor uses to insert into the index
+ * accessMethodObjectId: OID of index AM to use
+ * tableSpaceId: OID of tablespace to use
+ * classObjectId: array of index opclass OIDs, one per index column
+ * isprimary: index is a PRIMARY KEY
+ * istoast: index is a toast table's index
+ * isconstraint: index is owned by a PRIMARY KEY or UNIQUE constraint
+ * allow_system_table_mods: allow table to be a system catalog
+ * skip_build: true to skip the index_build() step for the moment; caller
+ * must do it later (typically via reindex_index())
  *
  * Returns OID of the created index.
- * ----------------------------------------------------------------
  */
 Oid
 index_create(Oid heapRelationId,
@@ -455,7 +467,8 @@ index_create(Oid heapRelationId,
 			 Oid accessMethodObjectId,
 			 Oid tableSpaceId,
 			 Oid *classObjectId,
-			 bool primary,
+			 bool isprimary,
+			 bool istoast,
 			 bool isconstraint,
 			 bool allow_system_table_mods,
 			 bool skip_build)
@@ -595,7 +608,7 @@ index_create(Oid heapRelationId,
 	 * ----------------
 	 */
 	UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
-						classObjectId, primary);
+						classObjectId, isprimary);
 
 	/*
 	 * Register constraint and dependencies for the index.
@@ -625,7 +638,7 @@ index_create(Oid heapRelationId,
 			char		constraintType;
 			Oid			conOid;
 
-			if (primary)
+			if (isprimary)
 				constraintType = CONSTRAINT_PRIMARY;
 			else if (indexInfo->ii_Unique)
 				constraintType = CONSTRAINT_UNIQUE;
@@ -736,28 +749,40 @@ index_create(Oid heapRelationId,
 	 * Similarly, if the caller specified skip_build then filling the index is
 	 * delayed till later (ALTER TABLE can save work in some cases with this).
 	 * Otherwise, we call the AM routine that constructs the index.
-	 *
-	 * In normal processing mode, the heap and index relations are closed, but
-	 * we continue to hold the ShareLock on the heap and the exclusive lock on
-	 * the index that we acquired above, until end of transaction.
 	 */
 	if (IsBootstrapProcessingMode())
 	{
 		index_register(heapRelationId, indexRelationId, indexInfo);
-		/* XXX shouldn't we close the heap and index rels here? */
 	}
 	else if (skip_build)
 	{
-		/* caller is responsible for filling the index later on */
-		relation_close(indexRelation, NoLock);
-		heap_close(heapRelation, NoLock);
+		/*
+		 * Caller is responsible for filling the index later on.  However,
+		 * we'd better make sure that the heap relation is correctly marked
+		 * as having an index.
+		 */
+		index_update_stats(heapRelation,
+						   true,
+						   isprimary,
+						   InvalidOid,
+						   heapRelation->rd_rel->reltuples);
+		/* Make the above update visible */
+		CommandCounterIncrement();
 	}
 	else
 	{
-		index_build(heapRelation, indexRelation, indexInfo);
-		/* index_build closes the passed rels */
+		index_build(heapRelation, indexRelation, indexInfo,
+					isprimary, istoast);
 	}
 
+	/*
+	 * Close the heap and index; but we keep the ShareLock on the heap and
+	 * the exclusive lock on the index that we acquired above, until end of
+	 * transaction.
+	 */
+	index_close(indexRelation);
+	heap_close(heapRelation, NoLock);
+
 	return indexRelationId;
 }
 
@@ -983,38 +1008,59 @@ FormIndexDatum(IndexInfo *indexInfo,
 }
 
 
-/* ----------------
- *		set relhasindex of relation's pg_class entry
+/*
+ * index_update_stats --- update pg_class entry after CREATE INDEX
  *
- * If isprimary is TRUE, we are defining a primary index, so also set
- * relhaspkey to TRUE.	Otherwise, leave relhaspkey alone.
+ * This routine updates the pg_class row of either an index or its parent
+ * relation after CREATE INDEX.  Its rather bizarre API is designed to
+ * ensure we can do all the necessary work in just one update.
  *
- * If reltoastidxid is not InvalidOid, also set reltoastidxid to that value.
- * This is only used for TOAST relations.
+ * hasindex: set relhasindex to this value
+ * isprimary: if true, set relhaspkey true; else no change
+ * reltoastidxid: if not InvalidOid, set reltoastidxid to this value;
+ *		else no change
+ * reltuples: set reltuples to this value
+ *
+ * relpages is also updated (using RelationGetNumberOfBlocks()).
  *
  * NOTE: an important side-effect of this operation is that an SI invalidation
  * message is sent out to all backends --- including me --- causing relcache
- * entries to be flushed or updated with the new hasindex data.  This must
- * happen even if we find that no change is needed in the pg_class row.
- * ----------------
+ * entries to be flushed or updated with the new data.  This must happen even
+ * if we find that no change is needed in the pg_class row.  When updating
+ * a heap entry, this ensures that other backends find out about the new
+ * index.  When updating an index, it's important because some index AMs
+ * expect a relcache flush to occur after REINDEX.
  */
-void
-setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
+static void
+index_update_stats(Relation rel, bool hasindex, bool isprimary,
+				   Oid reltoastidxid, double reltuples)
 {
+	BlockNumber	relpages = RelationGetNumberOfBlocks(rel);
+	Oid			relid = RelationGetRelid(rel);
 	Relation	pg_class;
 	HeapTuple	tuple;
-	Form_pg_class classtuple;
-	bool		dirty = false;
-	HeapScanDesc pg_class_scan = NULL;
+	Form_pg_class rd_rel;
+	bool		in_place_upd;
+	bool		dirty;
 
 	/*
-	 * Find the tuple to update in pg_class.  In bootstrap mode we can't use
-	 * heap_update, so cheat and overwrite the tuple in-place.	In normal
-	 * processing, make a copy to scribble on.
+	 * Find the tuple to update in pg_class.  Normally we make a copy of the
+	 * tuple using the syscache, modify it, and apply heap_update. But in
+	 * bootstrap mode we can't use heap_update, so we use a nontransactional
+	 * update, ie, overwrite the tuple in-place.
+	 *
+	 * We also must use an in-place update if reindexing pg_class itself,
+	 * because the target index may presently not be part of the set of
+	 * indexes that CatalogUpdateIndexes would update (see reindex_relation).
 	 */
 	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
 
-	if (!IsBootstrapProcessingMode())
+	in_place_upd = IsBootstrapProcessingMode() ||
+		ReindexIsProcessingHeap(RelationRelationId);
+
+restart:
+
+	if (!in_place_upd)
 	{
 		tuple = SearchSysCacheCopy(RELOID,
 								   ObjectIdGetDatum(relid),
@@ -1022,6 +1068,8 @@ setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
 	}
 	else
 	{
+		/* don't assume syscache will work */
+		HeapScanDesc pg_class_scan;
 		ScanKeyData key[1];
 
 		ScanKeyInit(&key[0],
@@ -1031,65 +1079,110 @@ setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
 
 		pg_class_scan = heap_beginscan(pg_class, SnapshotNow, 1, key);
 		tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
+		tuple = heap_copytuple(tuple);
+		heap_endscan(pg_class_scan);
 	}
 
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "could not find tuple for relation %u", relid);
-	classtuple = (Form_pg_class) GETSTRUCT(tuple);
-
-	/* Apply required updates */
+	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (pg_class_scan)
-		LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);
+	/* Apply required updates, if any, to copied tuple */
 
-	if (classtuple->relhasindex != hasindex)
+	dirty = false;
+	if (rd_rel->relhasindex != hasindex)
 	{
-		classtuple->relhasindex = hasindex;
+		rd_rel->relhasindex = hasindex;
 		dirty = true;
 	}
 	if (isprimary)
 	{
-		if (!classtuple->relhaspkey)
+		if (!rd_rel->relhaspkey)
 		{
-			classtuple->relhaspkey = true;
+			rd_rel->relhaspkey = true;
 			dirty = true;
 		}
 	}
 	if (OidIsValid(reltoastidxid))
 	{
-		Assert(classtuple->relkind == RELKIND_TOASTVALUE);
-		if (classtuple->reltoastidxid != reltoastidxid)
+		Assert(rd_rel->relkind == RELKIND_TOASTVALUE);
+		if (rd_rel->reltoastidxid != reltoastidxid)
 		{
-			classtuple->reltoastidxid = reltoastidxid;
+			rd_rel->reltoastidxid = reltoastidxid;
 			dirty = true;
 		}
 	}
-
-	if (pg_class_scan)
+	if (rd_rel->reltuples != (float4) reltuples)
 	{
-		MarkBufferDirty(pg_class_scan->rs_cbuf);
-		LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
-		/* Send out shared cache inval if necessary */
-		if (!IsBootstrapProcessingMode())
-			CacheInvalidateHeapTuple(pg_class, tuple);
+		rd_rel->reltuples = (float4) reltuples;
+		dirty = true;
 	}
-	else if (dirty)
+	if (rd_rel->relpages != (int32) relpages)
 	{
-		simple_heap_update(pg_class, &tuple->t_self, tuple);
+		rd_rel->relpages = (int32) relpages;
+		dirty = true;
+	}
 
-		/* Keep the catalog indexes up to date */
-		CatalogUpdateIndexes(pg_class, tuple);
+	/*
+	 * If anything changed, write out the tuple
+	 */
+	if (dirty)
+	{
+		if (in_place_upd)
+		{
+			heap_inplace_update(pg_class, tuple);
+		}
+		else
+		{
+			/*
+			 * Because PG allows concurrent CREATE INDEX commands, it's
+			 * possible that someone else tries to update the pg_class
+			 * row at about the same time we do.  Hence, instead of using
+			 * simple_heap_update(), we must use full heap_update() and
+			 * cope with HeapTupleUpdated result.  If we see that, just
+			 * go back and try the whole update again.
+			 */
+			HTSU_Result result;
+			ItemPointerData update_ctid;
+			TransactionId update_xmax;
+
+			result = heap_update(pg_class, &tuple->t_self, tuple,
+								 &update_ctid, &update_xmax,
+								 GetCurrentCommandId(), InvalidSnapshot,
+								 true /* wait for commit */ );
+			switch (result)
+			{
+				case HeapTupleSelfUpdated:
+					/* Tuple was already updated in current command? */
+					elog(ERROR, "tuple already updated by self");
+					break;
+
+				case HeapTupleMayBeUpdated:
+					/* done successfully */
+					break;
+
+				case HeapTupleUpdated:
+					heap_freetuple(tuple);
+					/* Must do CCI so we can see the updated tuple */
+					CommandCounterIncrement();
+					goto restart;
+
+				default:
+					elog(ERROR, "unrecognized heap_update status: %u", result);
+					break;
+			}
+
+			/* Keep the catalog indexes up to date */
+			CatalogUpdateIndexes(pg_class, tuple);
+		}
 	}
 	else
 	{
-		/* no need to change tuple, but force relcache rebuild anyway */
+		/* no need to change tuple, but force relcache inval anyway */
 		CacheInvalidateRelcacheByTuple(tuple);
 	}
 
-	if (!pg_class_scan)
-		heap_freetuple(tuple);
-	else
-		heap_endscan(pg_class_scan);
+	heap_freetuple(tuple);
 
 	heap_close(pg_class, RowExclusiveLock);
 }
@@ -1163,177 +1256,31 @@ setNewRelfilenode(Relation relation)
 }
 
 
-/*
- * This is invoked by the various index AMs once they have finished
- * constructing an index. Constructing an index involves counting the
- * number of tuples in both the relation and the index, so we take
- * advantage of the opportunity to update pg_class to ensure that the
- * planner takes advantage of the index we just created.  But, only
- * update statistics during normal index definitions, not for indices
- * on system catalogs created during bootstrap processing.	We must
- * close the relations before updating statistics to guarantee that
- * the relcache entries are flushed when we increment the command
- * counter in UpdateStats(). But we do not release any locks on the
- * relations; those will be held until end of transaction.
- */
-void
-IndexCloseAndUpdateStats(Relation heap, double heapTuples,
-						 Relation index, double indexTuples)
-{
-	Oid			hrelid = RelationGetRelid(heap);
-	Oid			irelid = RelationGetRelid(index);
-
-	if (!IsNormalProcessingMode())
-		return;
-
-	heap_close(heap, NoLock);
-	index_close(index);
-	UpdateStats(hrelid, heapTuples);
-	UpdateStats(irelid, indexTuples);
-}
-
-
-/* ----------------
- *		UpdateStats
- *
- * Update pg_class' relpages and reltuples statistics for the given relation
- * (which can be either a table or an index).  Note that this is not used
- * in the context of VACUUM, only CREATE INDEX.
- * ----------------
- */
-static void
-UpdateStats(Oid relid, double reltuples)
-{
-	Relation	whichRel;
-	Relation	pg_class;
-	HeapTuple	tuple;
-	BlockNumber relpages;
-	Form_pg_class rd_rel;
-	HeapScanDesc pg_class_scan = NULL;
-	bool		in_place_upd;
-
-	/*
-	 * This routine handles updates for both the heap and index relation
-	 * statistics.	In order to guarantee that we're able to *see* the index
-	 * relation tuple, we bump the command counter id here.  The index
-	 * relation tuple was created in the current transaction.
-	 */
-	CommandCounterIncrement();
-
-	/*
-	 * CommandCounterIncrement() flushes invalid cache entries, including
-	 * those for the heap and index relations for which we're updating
-	 * statistics.	Now that the cache is flushed, it's safe to open the
-	 * relation again.	We need the relation open in order to figure out how
-	 * many blocks it contains.
-	 */
-
-	/*
-	 * Grabbing lock here is probably redundant ...
-	 */
-	whichRel = relation_open(relid, ShareLock);
-
-	/*
-	 * Find the tuple to update in pg_class.  Normally we make a copy of the
-	 * tuple using the syscache, modify it, and apply heap_update. But in
-	 * bootstrap mode we can't use heap_update, so we cheat and overwrite the
-	 * tuple in-place.	(Note: as of PG 8.0 this isn't called during
-	 * bootstrap, but leave the code here for possible future use.)
-	 *
-	 * We also must cheat if reindexing pg_class itself, because the target
-	 * index may presently not be part of the set of indexes that
-	 * CatalogUpdateIndexes would update (see reindex_relation).  In this case
-	 * the stats updates will not be WAL-logged and so could be lost in a
-	 * crash.  This seems OK considering VACUUM does the same thing.
-	 */
-	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
-
-	in_place_upd = IsBootstrapProcessingMode() ||
-		ReindexIsProcessingHeap(RelationRelationId);
-
-	if (!in_place_upd)
-	{
-		tuple = SearchSysCacheCopy(RELOID,
-								   ObjectIdGetDatum(relid),
-								   0, 0, 0);
-	}
-	else
-	{
-		ScanKeyData key[1];
-
-		ScanKeyInit(&key[0],
-					ObjectIdAttributeNumber,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(relid));
-
-		pg_class_scan = heap_beginscan(pg_class, SnapshotNow, 1, key);
-		tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
-	}
-
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u", relid);
-	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
-
-	/*
-	 * Update statistics in pg_class, if they changed.	(Avoiding an
-	 * unnecessary update is not just a tiny performance improvement; it also
-	 * reduces the window wherein concurrent CREATE INDEX commands may
-	 * conflict.)
-	 */
-	relpages = RelationGetNumberOfBlocks(whichRel);
-
-	if (rd_rel->relpages != (int32) relpages ||
-		rd_rel->reltuples != (float4) reltuples)
-	{
-		if (in_place_upd)
-		{
-			/* Bootstrap or reindex case: overwrite fields in place. */
-			LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);
-			rd_rel->relpages = (int32) relpages;
-			rd_rel->reltuples = (float4) reltuples;
-			MarkBufferDirty(pg_class_scan->rs_cbuf);
-			LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
-			if (!IsBootstrapProcessingMode())
-				CacheInvalidateHeapTuple(pg_class, tuple);
-		}
-		else
-		{
-			/* During normal processing, must work harder. */
-			rd_rel->relpages = (int32) relpages;
-			rd_rel->reltuples = (float4) reltuples;
-			simple_heap_update(pg_class, &tuple->t_self, tuple);
-			CatalogUpdateIndexes(pg_class, tuple);
-		}
-	}
-
-	if (in_place_upd)
-		heap_endscan(pg_class_scan);
-	else
-		heap_freetuple(tuple);
-
-	/*
-	 * We shouldn't have to do this, but we do...  Modify the reldesc in place
-	 * with the new values so that the cache contains the latest copy.	(XXX
-	 * is this really still necessary?	The relcache will get fixed at next
-	 * CommandCounterIncrement, so why bother here?)
-	 */
-	whichRel->rd_rel->relpages = (int32) relpages;
-	whichRel->rd_rel->reltuples = (float4) reltuples;
-
-	heap_close(pg_class, RowExclusiveLock);
-	relation_close(whichRel, NoLock);
-}
-
-
 /*
  * index_build - invoke access-method-specific index build procedure
+ *
+ * On entry, the index's catalog entries are valid, and its physical disk
+ * file has been created but is empty.  We call the AM-specific build
+ * procedure to fill in the index contents.  We then update the pg_class
+ * entries of the index and heap relation as needed, using statistics
+ * returned by ambuild as well as data passed by the caller.
+ *
+ * Note: when reindexing an existing index, isprimary and istoast can be
+ * false; the index is already properly marked and need not be re-marked.
+ *
+ * Note: before Postgres 8.2, the passed-in heap and index Relations
+ * were automatically closed by this routine.  This is no longer the case.
+ * The caller opened 'em, and the caller should close 'em.
  */
 void
 index_build(Relation heapRelation,
 			Relation indexRelation,
-			IndexInfo *indexInfo)
+			IndexInfo *indexInfo,
+			bool isprimary,
+			bool istoast)
 {
 	RegProcedure procedure;
+	IndexBuildResult *stats;
 
 	/*
 	 * sanity checks
@@ -1347,10 +1294,30 @@ index_build(Relation heapRelation,
 	/*
 	 * Call the access method's build procedure
 	 */
-	OidFunctionCall3(procedure,
-					 PointerGetDatum(heapRelation),
-					 PointerGetDatum(indexRelation),
-					 PointerGetDatum(indexInfo));
+	stats = (IndexBuildResult *)
+		DatumGetPointer(OidFunctionCall3(procedure,
+										 PointerGetDatum(heapRelation),
+										 PointerGetDatum(indexRelation),
+										 PointerGetDatum(indexInfo)));
+	Assert(PointerIsValid(stats));
+
+	/*
+	 * Update heap and index pg_class rows
+	 */
+	index_update_stats(heapRelation,
+					   true,
+					   isprimary,
+					   istoast ? RelationGetRelid(indexRelation) : InvalidOid,
+					   stats->heap_tuples);
+
+	index_update_stats(indexRelation,
+					   false,
+					   false,
+					   InvalidOid,
+					   stats->index_tuples);
+
+	/* Make the updated versions visible */
+	CommandCounterIncrement();
 }
 
 
@@ -1674,12 +1641,8 @@ reindex_index(Oid indexId)
 		}
 
 		/* Initialize the index and rebuild */
-		index_build(heapRelation, iRel, indexInfo);
-
-		/*
-		 * index_build will close both the heap and index relations (but not
-		 * give up the locks we hold on them).	So we're done.
-		 */
+		/* Note: we do not need to re-establish pkey or toast settings */
+		index_build(heapRelation, iRel, indexInfo, false, false);
 	}
 	PG_CATCH();
 	{
@@ -1689,6 +1652,10 @@ reindex_index(Oid indexId)
 	}
 	PG_END_TRY();
 	ResetReindexProcessing();
+
+	/* Close rels, but keep locks */
+	index_close(iRel);
+	heap_close(heapRelation, NoLock);
 }
 
 /*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 431d8dcd08bfe971d3913f464fa93ca665dc8eba..ef57db4a0c02495d2811eb99a7c071999a2070ff 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.138 2006/03/05 15:58:24 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.139 2006/05/10 23:18:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -397,17 +397,8 @@ DefineIndex(RangeVar *heapRelation,
 
 	index_create(relationId, indexRelationName, indexRelationId,
 				 indexInfo, accessMethodId, tablespaceId, classObjectId,
-				 primary, isconstraint,
+				 primary, false, isconstraint,
 				 allowSystemTableMods, skip_build);
-
-	/*
-	 * We update the relation's pg_class tuple even if it already has
-	 * relhasindex = true.	This is needed to cause a shared-cache-inval
-	 * message to be sent for the pg_class tuple, which will cause other
-	 * backends to flush their relcache entries and in particular their cached
-	 * lists of the indexes for this relation.
-	 */
-	setRelhasindex(relationId, true, primary, InvalidOid);
 }
 
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f670ccf14eb02fe165ddd982b7c98b193b16c287..9da1d86c42c8d8b2f8d86cb2f7e7dd5a9479e0ab 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.183 2006/04/30 01:08:07 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.184 2006/05/10 23:18:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -6046,14 +6046,7 @@ AlterTableCreateToastTable(Oid relOid, bool silent)
 							   BTREE_AM_OID,
 							   rel->rd_rel->reltablespace,
 							   classObjectId,
-							   true, false, true, false);
-
-	/*
-	 * Update toast rel's pg_class entry to show that it has an index. The
-	 * index OID is stored into the reltoastidxid field for easy access by the
-	 * tuple toaster.
-	 */
-	setRelhasindex(toast_relid, true, true, toast_idxid);
+							   true, true, false, true, false);
 
 	/*
 	 * Store the toast table's OID in the parent relation's pg_class row
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7f13bae8099053fcf2b8a1567dec5ce24c33b5b5..4381369bc034b168b3cd6f1a51f8871c28665a1e 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.329 2006/05/03 22:45:26 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.330 2006/05/10 23:18:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -673,9 +673,10 @@ vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel,
  *		doing ANALYZE, but we always update these stats.  This routine works
  *		for both index and heap relation entries in pg_class.
  *
- *		We violate no-overwrite semantics here by storing new values for the
- *		statistics columns directly into the pg_class tuple that's already on
- *		the page.  The reason for this is that if we updated these tuples in
+ *		We violate transaction semantics here by overwriting the rel's
+ *		existing pg_class tuple with the new values.  This is reasonably
+ *		safe since the new values are correct whether or not this transaction
+ *		commits.  The reason for this is that if we updated these tuples in
  *		the usual way, vacuuming pg_class itself wouldn't work very well ---
  *		by the time we got done with a vacuum cycle, most of the tuples in
  *		pg_class would've been obsoleted.  Of course, this only works for
@@ -689,59 +690,57 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples,
 					bool hasindex)
 {
 	Relation	rd;
-	HeapTupleData rtup;
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
-	Buffer		buffer;
+	bool		dirty;
 
-	/*
-	 * update number of tuples and number of pages in pg_class
-	 */
 	rd = heap_open(RelationRelationId, RowExclusiveLock);
 
-	ctup = SearchSysCache(RELOID,
-						  ObjectIdGetDatum(relid),
-						  0, 0, 0);
+	/* Fetch a copy of the tuple to scribble on */
+	ctup = SearchSysCacheCopy(RELOID,
+							  ObjectIdGetDatum(relid),
+							  0, 0, 0);
 	if (!HeapTupleIsValid(ctup))
 		elog(ERROR, "pg_class entry for relid %u vanished during vacuuming",
 			 relid);
+	pgcform = (Form_pg_class) GETSTRUCT(ctup);
 
-	/* get the buffer cache tuple */
-	rtup.t_self = ctup->t_self;
-	ReleaseSysCache(ctup);
-	if (!heap_fetch(rd, SnapshotNow, &rtup, &buffer, false, NULL))
-		elog(ERROR, "pg_class entry for relid %u vanished during vacuuming",
-			 relid);
-
-	/* ensure no one else does this at the same time */
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	/* overwrite the existing statistics in the tuple */
-	pgcform = (Form_pg_class) GETSTRUCT(&rtup);
-	pgcform->relpages = (int32) num_pages;
-	pgcform->reltuples = (float4) num_tuples;
-	pgcform->relhasindex = hasindex;
+	/* Apply required updates, if any, to copied tuple */
 
+	dirty = false;
+	if (pgcform->relpages != (int32) num_pages)
+	{
+		pgcform->relpages = (int32) num_pages;
+		dirty = true;
+	}
+	if (pgcform->reltuples != (float4) num_tuples)
+	{
+		pgcform->reltuples = (float4) num_tuples;
+		dirty = true;
+	}
+	if (pgcform->relhasindex != hasindex)
+	{
+		pgcform->relhasindex = hasindex;
+		dirty = true;
+	}
 	/*
 	 * If we have discovered that there are no indexes, then there's no
 	 * primary key either.	This could be done more thoroughly...
 	 */
 	if (!hasindex)
-		pgcform->relhaspkey = false;
-
-	MarkBufferDirty(buffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+	{
+		if (pgcform->relhaspkey)
+		{
+			pgcform->relhaspkey = false;
+			dirty = true;
+		}
+	}
 
 	/*
-	 * Invalidate the tuple in the catcaches; this also arranges to flush the
-	 * relation's relcache entry.  (If we fail to commit for some reason, no
-	 * flush will occur, but no great harm is done since there are no
-	 * noncritical state updates here.)
+	 * If anything changed, write out the tuple
 	 */
-	CacheInvalidateHeapTuple(rd, &rtup);
-
-	ReleaseBuffer(buffer);
+	if (dirty)
+		heap_inplace_update(rd, ctup);
 
 	heap_close(rd, RowExclusiveLock);
 }
@@ -753,10 +752,11 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples,
  *		Update the whole-database statistics that are kept in its pg_database
  *		row, and the flat-file copy of pg_database.
  *
- *		We violate no-overwrite semantics here by storing new values for the
- *		statistics columns directly into the tuple that's already on the page.
- *		As with vac_update_relstats, this avoids leaving dead tuples behind
- *		after a VACUUM.
+ *		We violate transaction semantics here by overwriting the database's
+ *		existing pg_database tuple with the new values.  This is reasonably
+ *		safe since the new values are correct whether or not this transaction
+ *		commits.  As with vac_update_relstats, this avoids leaving dead tuples
+ *		behind after a VACUUM.
  *
  *		This routine is shared by full and lazy VACUUM.  Note that it is only
  *		applied after a database-wide VACUUM operation.
@@ -767,49 +767,24 @@ vac_update_dbstats(Oid dbid,
 				   TransactionId frozenXID)
 {
 	Relation	relation;
-	ScanKeyData entry[1];
-	SysScanDesc	scan;
 	HeapTuple	tuple;
-	Buffer		buf;
 	Form_pg_database dbform;
 
 	relation = heap_open(DatabaseRelationId, RowExclusiveLock);
 
-	ScanKeyInit(&entry[0],
-				ObjectIdAttributeNumber,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(dbid));
-
-	scan = systable_beginscan(relation, DatabaseOidIndexId, true,
-							  SnapshotNow, 1, entry);
-
-	tuple = systable_getnext(scan);
-
+	/* Fetch a copy of the tuple to scribble on */
+	tuple = SearchSysCacheCopy(DATABASEOID,
+							   ObjectIdGetDatum(dbid),
+							   0, 0, 0);
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "could not find tuple for database %u", dbid);
-
-	if (scan->irel)
-		buf = scan->iscan->xs_cbuf;
-	else
-		buf = scan->scan->rs_cbuf;
-
-	/* ensure no one else does this at the same time */
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-
 	dbform = (Form_pg_database) GETSTRUCT(tuple);
 
 	/* overwrite the existing statistics in the tuple */
 	dbform->datvacuumxid = vacuumXID;
 	dbform->datfrozenxid = frozenXID;
 
-	MarkBufferDirty(buf);
-
-	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-
-	/* invalidate the tuple in the cache so we'll see the change in cache */
-	CacheInvalidateHeapTuple(relation, tuple);
-
-	systable_endscan(scan);
+	heap_inplace_update(relation, tuple);
 
 	heap_close(relation, RowExclusiveLock);
 
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 67a9ef60ee9c8ef85fc511c9102e6d39b90f49c6..c1859a0c6d1ccc0fd6e245c848c3af36af116386 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/genam.h,v 1.59 2006/05/02 22:25:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/access/genam.h,v 1.60 2006/05/10 23:18:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,6 +20,15 @@
 #include "nodes/primnodes.h"
 
 
+/*
+ * Struct for statistics returned by ambuild
+ */
+typedef struct IndexBuildResult
+{
+	double		heap_tuples;	/* # of tuples seen in parent table */
+	double		index_tuples;	/* # of tuples inserted into index */
+} IndexBuildResult;
+
 /*
  * Struct for input arguments passed to ambulkdelete and amvacuumcleanup
  *
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index fdf2e8d86f5d9caeb13cdc36b6fb178f6e4d5aba..e016a527403ff5ed2018a6f7964d37cd0d966b7a 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.110 2006/04/24 22:24:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.111 2006/05/10 23:18:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -168,6 +168,7 @@ extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 				Buffer *buffer, ItemPointer ctid,
 				TransactionId *update_xmax, CommandId cid,
 				LockTupleMode mode, bool nowait);
+extern void heap_inplace_update(Relation relation, HeapTuple tuple);
 
 extern Oid	simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, ItemPointer tid);
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 9dacfcb8521c607a7b040c8d732a2c31edca595c..57db9f8493469bb14aaa3750c92bcb4d5e2d38e0 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/htup.h,v 1.81 2006/03/05 15:58:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/htup.h,v 1.82 2006/05/10 23:18:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -444,7 +444,7 @@ typedef HeapTupleData *HeapTuple;
 #define XLOG_HEAP_CLEAN		0x40
 #define XLOG_HEAP_NEWPAGE	0x50
 #define XLOG_HEAP_LOCK		0x60
-/* opcode 0x70 still free */
+#define XLOG_HEAP_INPLACE	0x70
 #define XLOG_HEAP_OPMASK	0x70
 /*
  * When we insert 1st item on new page in INSERT/UPDATE
@@ -545,4 +545,13 @@ typedef struct xl_heap_lock
 
 #define SizeOfHeapLock	(offsetof(xl_heap_lock, shared_lock) + sizeof(bool))
 
+/* This is what we need to know about in-place update */
+typedef struct xl_heap_inplace
+{
+	xl_heaptid	target;			/* updated tuple id */
+	/* TUPLE DATA FOLLOWS AT END OF STRUCT */
+} xl_heap_inplace;
+
+#define SizeOfHeapInplace	(offsetof(xl_heap_inplace, target) + SizeOfHeapTid)
+
 #endif   /* HTUP_H */
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index d7ea6f5846db526af6e41433b99cade3c2168960..6819426d49eff200e7e38f164a1c7e07b2b0f3d3 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -1,13 +1,13 @@
 /*-------------------------------------------------------------------------
  *
  * index.h
- *	  prototypes for index.c.
+ *	  prototypes for catalog/index.c.
  *
  *
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.65 2006/03/05 15:58:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.66 2006/05/10 23:18:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,7 +37,8 @@ extern Oid index_create(Oid heapRelationId,
 			 Oid accessMethodObjectId,
 			 Oid tableSpaceId,
 			 Oid *classObjectId,
-			 bool primary,
+			 bool isprimary,
+			 bool istoast,
 			 bool isconstraint,
 			 bool allow_system_table_mods,
 			 bool skip_build);
@@ -52,16 +53,13 @@ extern void FormIndexDatum(IndexInfo *indexInfo,
 			   Datum *values,
 			   bool *isnull);
 
-extern void IndexCloseAndUpdateStats(Relation heap, double heapTuples,
-						 Relation index, double indexTuples);
-
-extern void setRelhasindex(Oid relid, bool hasindex,
-			   bool isprimary, Oid reltoastidxid);
-
 extern void setNewRelfilenode(Relation relation);
 
-extern void index_build(Relation heapRelation, Relation indexRelation,
-			IndexInfo *indexInfo);
+extern void index_build(Relation heapRelation,
+			Relation indexRelation,
+			IndexInfo *indexInfo,
+			bool isprimary,
+			bool istoast);
 
 extern double IndexBuildHeapScan(Relation heapRelation,
 				   Relation indexRelation,
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ade008fa4e6af1cbad48b2c66a3ca167c3c4d2cc..09d57d940da589a67640a055f9eb98669e5635a6 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.410 2006/05/02 22:25:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.411 2006/05/10 23:18:39 tgl Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -672,7 +672,7 @@ DATA(insert OID = 336 (  btmarkpos		   PGNSP PGUID 12 f f t f v 1 2278 "2281" _n
 DESCR("btree(internal)");
 DATA(insert OID = 337 (  btrestrpos		   PGNSP PGUID 12 f f t f v 1 2278 "2281" _null_ _null_ _null_	btrestrpos - _null_ ));
 DESCR("btree(internal)");
-DATA(insert OID = 338 (  btbuild		   PGNSP PGUID 12 f f t f v 3 2278 "2281 2281 2281" _null_ _null_ _null_ btbuild - _null_ ));
+DATA(insert OID = 338 (  btbuild		   PGNSP PGUID 12 f f t f v 3 2281 "2281 2281 2281" _null_ _null_ _null_ btbuild - _null_ ));
 DESCR("btree(internal)");
 DATA(insert OID = 332 (  btbulkdelete	   PGNSP PGUID 12 f f t f v 4 2281 "2281 2281 2281 2281" _null_ _null_ _null_ btbulkdelete - _null_ ));
 DESCR("btree(internal)");
@@ -789,7 +789,7 @@ DATA(insert OID = 446 (  hashmarkpos	   PGNSP PGUID 12 f f t f v 1 2278 "2281" _
 DESCR("hash(internal)");
 DATA(insert OID = 447 (  hashrestrpos	   PGNSP PGUID 12 f f t f v 1 2278 "2281" _null_ _null_ _null_	hashrestrpos - _null_ ));
 DESCR("hash(internal)");
-DATA(insert OID = 448 (  hashbuild		   PGNSP PGUID 12 f f t f v 3 2278 "2281 2281 2281" _null_ _null_ _null_ hashbuild - _null_ ));
+DATA(insert OID = 448 (  hashbuild		   PGNSP PGUID 12 f f t f v 3 2281 "2281 2281 2281" _null_ _null_ _null_ hashbuild - _null_ ));
 DESCR("hash(internal)");
 DATA(insert OID = 442 (  hashbulkdelete    PGNSP PGUID 12 f f t f v 4 2281 "2281 2281 2281 2281" _null_ _null_ _null_ hashbulkdelete - _null_ ));
 DESCR("hash(internal)");
@@ -1055,7 +1055,7 @@ DATA(insert OID = 780 (  gistmarkpos	   PGNSP PGUID 12 f f t f v 1 2278 "2281" _
 DESCR("gist(internal)");
 DATA(insert OID = 781 (  gistrestrpos	   PGNSP PGUID 12 f f t f v 1 2278 "2281" _null_ _null_ _null_	gistrestrpos - _null_ ));
 DESCR("gist(internal)");
-DATA(insert OID = 782 (  gistbuild		   PGNSP PGUID 12 f f t f v 3 2278 "2281 2281 2281" _null_ _null_ _null_ gistbuild - _null_ ));
+DATA(insert OID = 782 (  gistbuild		   PGNSP PGUID 12 f f t f v 3 2281 "2281 2281 2281" _null_ _null_ _null_ gistbuild - _null_ ));
 DESCR("gist(internal)");
 DATA(insert OID = 776 (  gistbulkdelete    PGNSP PGUID 12 f f t f v 4 2281 "2281 2281 2281 2281" _null_ _null_ _null_ gistbulkdelete - _null_ ));
 DESCR("gist(internal)");
@@ -3833,7 +3833,7 @@ DATA(insert OID = 2736 (  ginmarkpos	   PGNSP PGUID 12 f f t f v 1 2278 "2281" _
 DESCR("gin(internal)");
 DATA(insert OID = 2737 (  ginrestrpos	   PGNSP PGUID 12 f f t f v 1 2278 "2281" _null_ _null_ _null_	ginrestrpos - _null_ ));
 DESCR("gin(internal)");
-DATA(insert OID = 2738 (  ginbuild		   PGNSP PGUID 12 f f t f v 3 2278 "2281 2281 2281" _null_ _null_ _null_ ginbuild - _null_ ));
+DATA(insert OID = 2738 (  ginbuild		   PGNSP PGUID 12 f f t f v 3 2281 "2281 2281 2281" _null_ _null_ _null_ ginbuild - _null_ ));
 DESCR("gin(internal)");
 DATA(insert OID = 2739 (  ginbulkdelete    PGNSP PGUID 12 f f t f v 4 2281 "2281 2281 2281 2281" _null_ _null_ _null_ ginbulkdelete - _null_ ));
 DESCR("gin(internal)");