From a56a016ceb612cdee1ddc5990682f36d541e5b07 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 24 Sep 2003 18:54:02 +0000
Subject: [PATCH] Repair some REINDEX problems per recent discussions.  The
 relcache is now able to cope with assigning new relfilenode values to
 nailed-in-cache indexes, so they can be reindexed using the fully crash-safe
 method.  This leaves only shared system indexes as special cases.  Remove the
 'index deactivation' code, since it provides no useful protection in the
 shared- index case.  Require reindexing of shared indexes to be done in
 standalone mode, but remove other restrictions on REINDEX.  -P
 (IgnoreSystemIndexes) now prevents using indexes for lookups, but does not
 disable index updates. It is therefore safe to allow from PGOPTIONS.  Upshot:
 reindexing system catalogs can be done without a standalone backend for all
 cases except shared catalogs.

---
 doc/src/sgml/ref/postgres-ref.sgml   |   8 +-
 doc/src/sgml/ref/reindex.sgml        | 141 ++++++---
 src/backend/access/index/genam.c     |  29 +-
 src/backend/access/transam/xact.c    |   5 +-
 src/backend/catalog/index.c          | 447 ++++++++-------------------
 src/backend/catalog/pg_largeobject.c |  44 ++-
 src/backend/commands/functioncmds.c  |  21 +-
 src/backend/commands/indexcmds.c     | 171 +++++-----
 src/backend/commands/vacuum.c        |  30 +-
 src/backend/executor/execUtils.c     |   7 +-
 src/backend/executor/nodeIndexscan.c |   8 +-
 src/backend/storage/ipc/sinval.c     |   8 +-
 src/backend/tcop/postgres.c          |  11 +-
 src/backend/tcop/utility.c           |   4 +-
 src/backend/utils/cache/relcache.c   | 204 +++++++++---
 src/backend/utils/cache/syscache.c   |  12 +-
 src/backend/utils/init/miscinit.c    |  59 +++-
 src/include/catalog/index.h          |  13 +-
 src/include/miscadmin.h              |  13 +-
 src/include/utils/errcodes.h         |   3 +-
 src/include/utils/rel.h              |   8 +-
 src/include/utils/relcache.h         |   4 +-
 22 files changed, 618 insertions(+), 632 deletions(-)

diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml
index b80c9caafac..279d8875e0e 100644
--- a/doc/src/sgml/ref/postgres-ref.sgml
+++ b/doc/src/sgml/ref/postgres-ref.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/postgres-ref.sgml,v 1.36 2003/09/18 20:30:15 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/postgres-ref.sgml,v 1.37 2003/09/24 18:54:01 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -177,9 +177,9 @@ PostgreSQL documentation
       <term><option>-P</option></term>
       <listitem>
        <para>
-	Ignore system indexes while scanning/updating system tables. The
-	<command>REINDEX</command> command for system tables/indexes
-	requires this option to be used.
+	Ignore system indexes when reading system tables (but still update
+	the indexes when modifying the tables).  This is useful when
+	recovering from damaged system indexes.
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml
index 29b96e462c3..d945112de79 100644
--- a/doc/src/sgml/ref/reindex.sgml
+++ b/doc/src/sgml/ref/reindex.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/reindex.sgml,v 1.20 2003/09/11 21:42:20 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/reindex.sgml,v 1.21 2003/09/24 18:54:01 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -56,43 +56,6 @@ REINDEX { DATABASE | TABLE | INDEX } <replaceable class="PARAMETER">name</replac
     </listitem>
    </itemizedlist>
   </para>
-
-  <para>
-   If you suspect corruption of an index on a user table, you can
-   simply rebuild that index, or all indexes on the table, using
-   <command>REINDEX INDEX</command> or <command>REINDEX
-   TABLE</command>.  Another approach to dealing with a corrupted
-   user-table index is just to drop and recreate it.  This may in fact
-   be preferable if you would like to maintain some semblance of
-   normal operation on the table meanwhile.  <command>REINDEX</>
-   acquires exclusive lock on the table, while <command>CREATE
-   INDEX</> only locks out writes not reads of the table.
-  </para>
-
-  <para>
-   Things are more difficult if you need to recover from corruption of
-   an index on a system table.  In this case it's important for the
-   system to not have used any of the suspect indexes itself.
-   (Indeed, in this sort of scenario you may find that server
-   processes are crashing immediately at start-up, due to reliance on
-   the corrupted indexes.)  To recover safely, the server must be shut
-   down and a stand-alone <productname>PostgreSQL</productname> server
-   must be started instead with the command-line options
-   <option>-O</option> and <option>-P</option>.  (These options allow
-   system table modifications and prevent use of system indexes,
-   respectively.)  Then, <command>REINDEX DATABASE</>,
-   <command>REINDEX TABLE</>, or <command>REINDEX INDEX</> can be
-   issued, depending on how much you want to reconstruct.  If in
-   doubt, use <command>REINDEX DATABASE FORCE</> to force
-   reconstruction of all system indexes in the database.  Then quit
-   the standalone server session and restart the real server.
-  </para>
-
-  <para>
-   See the <xref linkend="app-postgres"> reference page for more
-   information about how to interact with the stand-alone server
-   interface.
-  </para>
  </refsect1>
   
  <refsect1>
@@ -104,8 +67,8 @@ REINDEX { DATABASE | TABLE | INDEX } <replaceable class="PARAMETER">name</replac
     <listitem>
      <para>
       Recreate all system indexes of a specified database. Indexes on
-      user tables are not included. This form of <command>REINDEX</>
-      can only be used in stand-alone mode (see above).
+      user tables are not processed.  Also, indexes on shared system
+      catalogs are skipped except in stand-alone mode (see below).
      </para>
     </listitem>
    </varlistentry>
@@ -114,7 +77,8 @@ REINDEX { DATABASE | TABLE | INDEX } <replaceable class="PARAMETER">name</replac
     <term><literal>TABLE</literal></term>
     <listitem>
      <para>
-      Recreate all indexes of a specified table.
+      Recreate all indexes of a specified table.  If the table has a
+      secondary <quote>TOAST</> table, that is reindexed as well.
      </para>
     </listitem>
    </varlistentry>
@@ -142,16 +106,93 @@ REINDEX { DATABASE | TABLE | INDEX } <replaceable class="PARAMETER">name</replac
     <term><literal>FORCE</literal></term>
     <listitem>
      <para>
-      Force rebuild of system indexes.  Without this key word,
-      <command>REINDEX</> skips system indexes that are not marked
-      invalid.  <literal>FORCE</> is irrelevant for <command>REINDEX
-      INDEX</> or when reindexing user indexes.
+      This is an obsolete option; it is ignored if specified.
      </para>
     </listitem>
    </varlistentry>
   </variablelist>
  </refsect1>
 
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   If you suspect corruption of an index on a user table, you can
+   simply rebuild that index, or all indexes on the table, using
+   <command>REINDEX INDEX</command> or <command>REINDEX
+   TABLE</command>.  Another approach to dealing with a corrupted
+   user-table index is just to drop and recreate it.  This may in fact
+   be preferable if you would like to maintain some semblance of
+   normal operation on the table meanwhile.  <command>REINDEX</>
+   acquires exclusive lock on the table, while <command>CREATE
+   INDEX</> only locks out writes not reads of the table.
+  </para>
+
+  <para>
+   Things are more difficult if you need to recover from corruption of
+   an index on a system table.  In this case it's important for the
+   system to not have used any of the suspect indexes itself.
+   (Indeed, in this sort of scenario you may find that server
+   processes are crashing immediately at start-up, due to reliance on
+   the corrupted indexes.)  To recover safely, the server must be started
+   with the <option>-P</option> option, which prevents it from using
+   indexes for system catalog lookups.
+  </para>
+
+  <para>
+   One way to do this is to shut down the postmaster and start a stand-alone
+   <productname>PostgreSQL</productname> server
+   with the <option>-P</option> option included on its command line.
+   Then, <command>REINDEX DATABASE</>,
+   <command>REINDEX TABLE</>, or <command>REINDEX INDEX</> can be
+   issued, depending on how much you want to reconstruct.  If in
+   doubt, use <command>REINDEX DATABASE</> to select
+   reconstruction of all system indexes in the database.  Then quit
+   the standalone server session and restart the regular server.
+   See the <xref linkend="app-postgres"> reference page for more
+   information about how to interact with the stand-alone server
+   interface.
+  </para>
+
+  <para>
+   Alternatively, a regular server session can be started with
+   <option>-P</option> included in its command line options.
+   The method for doing this varies across clients, but in all
+   <application>libpq</>-based clients, it is possible to set
+   the <envar>PGOPTIONS</envar> environment variable to <literal>-P</>
+   before starting the client.  Note that while this method does not
+   require locking out other clients, it may still be wise to prevent
+   other users from connecting to the damaged database until repairs
+   have been completed.
+  </para>
+
+  <para>
+   If corruption is suspected in the indexes of any of the shared
+   system catalogs (<structname>pg_database</structname>,
+   <structname>pg_group</structname>, or
+   <structname>pg_shadow</structname>), then a standalone server
+   must be used to repair it.  <command>REINDEX</> will not process
+   shared catalogs in multiuser mode.
+  </para>
+
+  <para>
+   For all indexes except the shared system catalogs, <command>REINDEX</>
+   is crash-safe and transaction-safe.  <command>REINDEX</> is not
+   crash-safe for shared indexes, which is why this case is disallowed
+   during normal operation.  If a failure occurs while reindexing one
+   of these catalogs in standalone mode, it is important that the failure
+   be rectified and the <command>REINDEX</> operation redone
+   before attempting to restart the regular server.
+  </para>
+
+  <para>
+   Prior to <productname>PostgreSQL</productname> 7.4, <command>REINDEX
+   TABLE</> did not automatically process TOAST tables, and so those had
+   to be reindexed by separate commands.  This is still possible, but
+   redundant.
+  </para>
+ </refsect1>
+
  <refsect1>
   <title>Examples</title>
 
@@ -172,11 +213,15 @@ REINDEX INDEX my_index;
   </para>
 
   <para>
-   Rebuild all system indexes (this will only work in a stand-alone
-   server session):
+   Rebuild all system indexes in a particular database, without trusting them
+   to be valid already:
 
 <programlisting>
-REINDEX DATABASE my_database FORCE;
+$ <userinput>export PGOPTIONS="-P"</userinput>
+$ <userinput>psql broken_db</userinput>
+...
+broken_db=> REINDEX DATABASE broken_db;
+broken_db=> \q
 </programlisting>
   </para>
  </refsect1>
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index eaa0d172e54..a362cd8cfaf 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/index/genam.c,v 1.40 2003/08/04 02:39:57 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/index/genam.c,v 1.41 2003/09/24 18:54:01 tgl Exp $
  *
  * NOTES
  *	  many of the old access method routines have been turned into
@@ -184,21 +184,32 @@ systable_beginscan(Relation heapRelation,
 				   int nkeys, ScanKey key)
 {
 	SysScanDesc sysscan;
+	Relation	irel;
+
+	if (indexOK && !IsIgnoringSystemIndexes())
+	{
+		/* We assume it's a system index, so index_openr is OK */
+		irel = index_openr(indexRelname);
+
+		if (ReindexIsProcessingIndex(RelationGetRelid(irel)))
+		{
+			/* oops, can't use index that's being rebuilt */
+			index_close(irel);
+			irel = NULL;
+		}
+	}
+	else
+		irel = NULL;
 
 	sysscan = (SysScanDesc) palloc(sizeof(SysScanDescData));
 
 	sysscan->heap_rel = heapRelation;
+	sysscan->irel = irel;
 
-	if (indexOK &&
-		heapRelation->rd_rel->relhasindex &&
-		!IsIgnoringSystemIndexes())
+	if (irel)
 	{
-		Relation	irel;
 		int			i;
 
-		/* We assume it's a system index, so index_openr is OK */
-		sysscan->irel = irel = index_openr(indexRelname);
-
 		/*
 		 * Change attribute numbers to be index column numbers.
 		 *
@@ -210,13 +221,13 @@ systable_beginscan(Relation heapRelation,
 			Assert(key[i].sk_attno == irel->rd_index->indkey[i]);
 			key[i].sk_attno = i + 1;
 		}
+
 		sysscan->iscan = index_beginscan(heapRelation, irel, snapshot,
 										 nkeys, key);
 		sysscan->scan = NULL;
 	}
 	else
 	{
-		sysscan->irel = NULL;
 		sysscan->scan = heap_beginscan(heapRelation, snapshot, nkeys, key);
 		sysscan->iscan = NULL;
 	}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e632ae9e1f4..e17a3f3c372 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.152 2003/08/08 21:41:28 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.153 2003/09/24 18:54:01 tgl Exp $
  *
  * NOTES
  *		Transaction aborts can now occur two ways:
@@ -834,8 +834,6 @@ StartTransaction(void)
 	 */
 	s->state = TRANS_START;
 
-	SetReindexProcessing(false);
-
 	/*
 	 * generate a new transaction id
 	 */
@@ -1085,6 +1083,7 @@ AbortTransaction(void)
 	AtEOXact_Namespace(false);
 	AtEOXact_CatCache(false);
 	AtEOXact_Files();
+	SetReindexProcessing(InvalidOid, InvalidOid);
 	pgstat_count_xact_rollback();
 
 	/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 02eba08af41..ed560fb8915 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.216 2003/09/23 01:51:09 inoue Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.217 2003/09/24 18:54:01 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -76,27 +76,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 					Oid *classOids,
 					bool primary);
 static Oid	IndexGetRelation(Oid indexId);
-static bool activate_index(Oid indexId, bool activate, bool inplace);
 
 
-static bool reindexing = false;
-
-
-bool
-SetReindexProcessing(bool reindexmode)
-{
-	bool		old = reindexing;
-
-	reindexing = reindexmode;
-	return old;
-}
-
-bool
-IsReindexProcessing(void)
-{
-	return reindexing;
-}
-
 /*
  *		ConstructTupleDescriptor
  *
@@ -498,8 +479,6 @@ index_create(Oid heapRelationId,
 	Oid			indexoid;
 	int			i;
 
-	SetReindexProcessing(false);
-
 	/*
 	 * Only SELECT ... FOR UPDATE are allowed while doing this
 	 */
@@ -973,46 +952,6 @@ FormIndexDatum(IndexInfo *indexInfo,
 }
 
 
-/* ---------------------------------------------
- *		Indexes of the relation active ?
- *
- * Caller must hold an adequate lock on the relation to ensure the
- * answer won't be changing.
- * ---------------------------------------------
- */
-bool
-IndexesAreActive(Relation heaprel)
-{
-	bool		isactive;
-	Relation	indexRelation;
-	HeapScanDesc scan;
-	ScanKeyData entry;
-
-	if (heaprel->rd_rel->relkind != RELKIND_RELATION &&
-		heaprel->rd_rel->relkind != RELKIND_TOASTVALUE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("relation \"%s\" isn't an indexable relation",
-						RelationGetRelationName(heaprel))));
-
-	/* If pg_class.relhasindex is set, indexes are active */
-	isactive = heaprel->rd_rel->relhasindex;
-	if (isactive)
-		return isactive;
-
-	/* Otherwise, look to see if there are any indexes */
-	indexRelation = heap_openr(IndexRelationName, AccessShareLock);
-	ScanKeyEntryInitialize(&entry, 0,
-						   Anum_pg_index_indrelid, F_OIDEQ,
-						   ObjectIdGetDatum(RelationGetRelid(heaprel)));
-	scan = heap_beginscan(indexRelation, SnapshotNow, 1, &entry);
-	if (heap_getnext(scan, ForwardScanDirection) == NULL)
-		isactive = true;		/* no indexes, so report "active" */
-	heap_endscan(scan);
-	heap_close(indexRelation, AccessShareLock);
-	return isactive;
-}
-
 /* ----------------
  *		set relhasindex of relation's pg_class entry
  *
@@ -1038,12 +977,13 @@ setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
 	HeapScanDesc pg_class_scan = NULL;
 
 	/*
-	 * Find the tuple to update in pg_class.
+	 * 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.
 	 */
 	pg_class = heap_openr(RelationRelationName, RowExclusiveLock);
 
-	if (!IsIgnoringSystemIndexes() &&
-		(!IsReindexProcessing() || pg_class->rd_rel->relhasindex))
+	if (!IsBootstrapProcessingMode())
 	{
 		tuple = SearchSysCacheCopy(RELOID,
 								   ObjectIdGetDatum(relid),
@@ -1064,15 +1004,13 @@ setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
 
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "could not find tuple for relation %u", relid);
+	classtuple = (Form_pg_class) GETSTRUCT(tuple);
+
+	/* Apply required updates */
 
-	/*
-	 * Update fields in the pg_class tuple.
-	 */
 	if (pg_class_scan)
 		LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);
 
-	classtuple = (Form_pg_class) GETSTRUCT(tuple);
-
 	if (classtuple->relhasindex != hasindex)
 	{
 		classtuple->relhasindex = hasindex;
@@ -1141,80 +1079,48 @@ setNewRelfilenode(Relation relation)
 	Relation	pg_class;
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
-	HeapScanDesc pg_class_scan = NULL;
-	bool		in_place_upd;
 	RelationData workrel;
 
-	Assert(!IsSystemRelation(relation) || IsToastRelation(relation) ||
+	/* Can't change relfilenode for nailed tables (indexes ok though) */
+	Assert(!relation->rd_isnailed ||
 		   relation->rd_rel->relkind == RELKIND_INDEX);
+	/* Can't change for shared tables or indexes */
+	Assert(!relation->rd_rel->relisshared);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = newoid();
 
 	/*
-	 * Find the RELATION relation tuple for the given relation.
+	 * Find the pg_class tuple for the given relation.  This is not used
+	 * during bootstrap, so okay to use heap_update always.
 	 */
 	pg_class = heap_openr(RelationRelationName, RowExclusiveLock);
 
-	in_place_upd = IsIgnoringSystemIndexes();
-
-	if (!in_place_upd)
-	{
-		tuple = SearchSysCacheCopy(RELOID,
-							ObjectIdGetDatum(RelationGetRelid(relation)),
-								   0, 0, 0);
-	}
-	else
-	{
-		ScanKeyData key[1];
-
-		ScanKeyEntryInitialize(&key[0], 0,
-							   ObjectIdAttributeNumber,
-							   F_OIDEQ,
-						   ObjectIdGetDatum(RelationGetRelid(relation)));
-
-		pg_class_scan = heap_beginscan(pg_class, SnapshotNow, 1, key);
-		tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
-	}
-
+	tuple = SearchSysCacheCopy(RELOID,
+							   ObjectIdGetDatum(RelationGetRelid(relation)),
+							   0, 0, 0);
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "could not find tuple for relation %u",
 			 RelationGetRelid(relation));
 	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
 
-	/* schedule unlinking old relfilenode */
-	smgrunlink(DEFAULT_SMGR, relation);
-
 	/* create another storage file. Is it a little ugly ? */
+	/* NOTE: any conflict in relfilenode value will be caught here */
 	memcpy((char *) &workrel, relation, sizeof(RelationData));
 	workrel.rd_fd = -1;
 	workrel.rd_node.relNode = newrelfilenode;
 	heap_storage_create(&workrel);
 	smgrclose(DEFAULT_SMGR, &workrel);
 
+	/* schedule unlinking old relfilenode */
+	smgrunlink(DEFAULT_SMGR, relation);
+
 	/* update the pg_class row */
-	if (in_place_upd)
-	{
-		LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);
-		rd_rel->relfilenode = newrelfilenode;
-		LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
-		WriteNoReleaseBuffer(pg_class_scan->rs_cbuf);
-		BufferSync();
-		/* Send out shared cache inval if necessary */
-		if (!IsBootstrapProcessingMode())
-			CacheInvalidateHeapTuple(pg_class, tuple);
-	}
-	else
-	{
-		rd_rel->relfilenode = newrelfilenode;
-		simple_heap_update(pg_class, &tuple->t_self, tuple);
-		CatalogUpdateIndexes(pg_class, tuple);
-	}
+	rd_rel->relfilenode = newrelfilenode;
+	simple_heap_update(pg_class, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(pg_class, tuple);
 
-	if (!pg_class_scan)
-		heap_freetuple(tuple);
-	else
-		heap_endscan(pg_class_scan);
+	heap_freetuple(tuple);
 
 	heap_close(pg_class, RowExclusiveLock);
 
@@ -1264,11 +1170,21 @@ UpdateStats(Oid relid, double reltuples)
 	whichRel = relation_open(relid, ShareLock);
 
 	/*
-	 * Find the RELATION relation tuple for the given relation.
+	 * 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.
+	 *
+	 * 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_openr(RelationRelationName, RowExclusiveLock);
 
-	in_place_upd = (IsIgnoringSystemIndexes() || IsReindexProcessing());
+	in_place_upd = IsBootstrapProcessingMode() ||
+		ReindexIsProcessingHeap(RelationGetRelid(pg_class));
 
 	if (!in_place_upd)
 	{
@@ -1291,6 +1207,7 @@ UpdateStats(Oid relid, double reltuples)
 
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "could not find tuple for relation %u", relid);
+	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
 
 	/*
 	 * Figure values to insert.
@@ -1331,18 +1248,12 @@ UpdateStats(Oid relid, double reltuples)
 	 * also reduces the window wherein concurrent CREATE INDEX commands
 	 * may conflict.)
 	 */
-	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
-
 	if (rd_rel->relpages != (int32) relpages ||
 		rd_rel->reltuples != (float4) reltuples)
 	{
 		if (in_place_upd)
 		{
-			/*
-			 * At bootstrap time, we don't need to worry about concurrency
-			 * or visibility of changes, so we cheat.  Also cheat if
-			 * REINDEX.
-			 */
+			/* 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;
@@ -1562,10 +1473,13 @@ IndexBuildHeapScan(Relation heapRelation,
 					 * should not see any tuples inserted by open
 					 * transactions --- unless it's our own transaction.
 					 * (Consider INSERT followed by CREATE INDEX within a
-					 * transaction.)
+					 * transaction.)  An exception occurs when reindexing
+					 * a system catalog, because we often release lock on
+					 * system catalogs before committing.
 					 */
 					if (!TransactionIdIsCurrentTransactionId(
-							  HeapTupleHeaderGetXmin(heapTuple->t_data)))
+							  HeapTupleHeaderGetXmin(heapTuple->t_data))
+						&& !IsSystemRelation(heapRelation))
 						elog(ERROR, "concurrent insert in progress");
 					indexIt = true;
 					tupleIsAlive = true;
@@ -1577,10 +1491,13 @@ IndexBuildHeapScan(Relation heapRelation,
 					 * should not see any tuples deleted by open
 					 * transactions --- unless it's our own transaction.
 					 * (Consider DELETE followed by CREATE INDEX within a
-					 * transaction.)
+					 * transaction.)  An exception occurs when reindexing
+					 * a system catalog, because we often release lock on
+					 * system catalogs before committing.
 					 */
 					if (!TransactionIdIsCurrentTransactionId(
-							  HeapTupleHeaderGetXmax(heapTuple->t_data)))
+							  HeapTupleHeaderGetXmax(heapTuple->t_data))
+						&& !IsSystemRelation(heapRelation))
 						elog(ERROR, "concurrent delete in progress");
 					indexIt = true;
 					tupleIsAlive = false;
@@ -1690,81 +1607,57 @@ IndexGetRelation(Oid indexId)
 	return result;
 }
 
-/* ---------------------------------
- * activate_index -- activate/deactivate the specified index.
- *		Note that currently PostgreSQL doesn't hold the
- *		status per index
- * ---------------------------------
- */
-static bool
-activate_index(Oid indexId, bool activate, bool inplace)
-{
-	if (!activate)				/* Currently does nothing */
-		return true;
-	return reindex_index(indexId, false, inplace);
-}
-
-/* --------------------------------
- * reindex_index - This routine is used to recreate an index
- * --------------------------------
+/*
+ * reindex_index - This routine is used to recreate a single index
  */
-bool
-reindex_index(Oid indexId, bool force, bool inplace)
+void
+reindex_index(Oid indexId)
 {
 	Relation	iRel,
 				heapRelation;
 	IndexInfo  *indexInfo;
 	Oid			heapId;
-	bool		old;
+	bool		inplace;
 
 	/*
 	 * Open our index relation and get an exclusive lock on it.
 	 *
-	 * Note: doing this before opening the parent heap relation means there's
-	 * a possibility for deadlock failure against another xact that is
-	 * doing normal accesses to the heap and index.  However, it's not
-	 * real clear why you'd be needing to do REINDEX on a table that's in
-	 * active use, so I'd rather have the protection of making sure the
-	 * index is locked down.
+	 * Note: for REINDEX INDEX, doing this before opening the parent heap
+	 * relation means there's a possibility for deadlock failure against
+	 * another xact that is doing normal accesses to the heap and index.
+	 * However, it's not real clear why you'd be wanting to do REINDEX INDEX
+	 * on a table that's in active use, so I'd rather have the protection of
+	 * making sure the index is locked down.  In the REINDEX TABLE and
+	 * REINDEX DATABASE cases, there is no problem because caller already
+	 * holds exclusive lock on the parent table.
 	 */
 	iRel = index_open(indexId);
 	LockRelation(iRel, AccessExclusiveLock);
 
-	old = SetReindexProcessing(true);
-
 	/* Get OID of index's parent table */
 	heapId = iRel->rd_index->indrelid;
 
-	/* Open the parent heap relation */
+	/* Open and lock the parent heap relation */
 	heapRelation = heap_open(heapId, AccessExclusiveLock);
 
+	SetReindexProcessing(heapId, indexId);
+
 	/*
 	 * If it's a shared index, we must do inplace processing (because we
-	 * have no way to update relfilenode in other databases).  Also, if
-	 * it's a nailed-in-cache index, we must do inplace processing because
-	 * the relcache can't cope with changing its relfilenode.
+	 * have no way to update relfilenode in other databases).  Otherwise
+	 * we can do it the normal transaction-safe way.
 	 *
-	 * In either of these cases, we are definitely processing a system index,
-	 * so we'd better be ignoring system indexes.
+	 * Since inplace processing isn't crash-safe, we only allow it in a
+	 * standalone backend.  (In the REINDEX TABLE and REINDEX DATABASE cases,
+	 * the caller should have detected this.)
 	 */
-	if (iRel->rd_rel->relisshared)
-	{
-		if (!IsIgnoringSystemIndexes())
-			ereport(ERROR,
-					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				   errmsg("the target relation %u is shared", indexId)));
-		inplace = true;
-	}
-#ifndef ENABLE_REINDEX_NAILED_RELATIONS
-	if (iRel->rd_isnailed)
-	{
-		if (!IsIgnoringSystemIndexes())
-			ereport(ERROR,
-					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				   errmsg("the target relation %u is nailed", indexId)));
-		inplace = true;
-	}
-#endif /* ENABLE_REINDEX_NAILED_RELATIONS */
+	inplace = iRel->rd_rel->relisshared;
+
+	if (inplace && IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("shared index \"%s\" can only be reindexed in standalone mode",
+						RelationGetRelationName(iRel))));
 
 	/* Fetch info needed for index_build */
 	indexInfo = BuildIndexInfo(iRel);
@@ -1797,160 +1690,94 @@ reindex_index(Oid indexId, bool force, bool inplace)
 	 * index_build will close both the heap and index relations (but not
 	 * give up the locks we hold on them).	So we're done.
 	 */
-
-	SetReindexProcessing(old);
-
-	return true;
+	SetReindexProcessing(InvalidOid, InvalidOid);
 }
 
 /*
- * ----------------------------
- * activate_indexes_of_a_table
- *	activate/deactivate indexes of the specified table.
+ * reindex_relation - This routine is used to recreate all indexes
+ * of a relation (and its toast relation too, if any).
  *
- * Caller must already hold exclusive lock on the table.
- * ----------------------------
+ * Returns true if any indexes were rebuilt.
  */
 bool
-activate_indexes_of_a_table(Relation heaprel, bool activate)
+reindex_relation(Oid relid)
 {
-	if (IndexesAreActive(heaprel))
-	{
-		if (!activate)
-			setRelhasindex(RelationGetRelid(heaprel), false, false,
-						   InvalidOid);
-		else
-			return false;
-	}
-	else
-	{
-		if (activate)
-			reindex_relation(RelationGetRelid(heaprel), false);
-		else
-			return false;
-	}
-	return true;
-}
-
-/* --------------------------------
- * reindex_relation - This routine is used to recreate indexes
- * of a relation.
- * --------------------------------
- */
-bool
-reindex_relation(Oid relid, bool force)
-{
-	Relation	indexRelation;
-	ScanKeyData entry;
-	HeapScanDesc scan;
-	HeapTuple	indexTuple;
-	bool		old,
-				reindexed;
-	bool		deactivate_needed,
-				overwrite;
 	Relation	rel;
-
-	overwrite = deactivate_needed = false;
+	Oid			toast_relid;
+	bool		is_pg_class;
+	bool		result;
+	List	   *indexIds,
+			   *doneIndexes,
+			   *indexId;
 
 	/*
 	 * Ensure to hold an exclusive lock throughout the transaction. The
-	 * lock could be less intensive (in the non-overwrite path) but for
-	 * now it's AccessExclusiveLock for simplicity.
+	 * lock could perhaps be less intensive (in the non-overwrite case)
+	 * but for now it's AccessExclusiveLock for simplicity.
 	 */
 	rel = heap_open(relid, AccessExclusiveLock);
 
+	toast_relid = rel->rd_rel->reltoastrelid;
+
 	/*
-	 * ignore the indexes of the target system relation while processing
-	 * reindex.
+	 * Get the list of index OIDs for this relation.  (We trust to the
+	 * relcache to get this with a sequential scan if ignoring system
+	 * indexes.)
 	 */
-	if (!IsIgnoringSystemIndexes() &&
-		IsSystemRelation(rel) && !IsToastRelation(rel))
-		deactivate_needed = true;
+	indexIds = RelationGetIndexList(rel);
 
 	/*
-	 * Shared system indexes must be overwritten because it's impossible
-	 * to update pg_class tuples of all databases.
+	 * reindex_index will attempt to update the pg_class rows for the
+	 * relation and index.  If we are processing pg_class itself, we
+	 * want to make sure that the updates do not try to insert index
+	 * entries into indexes we have not processed yet.  (When we are
+	 * trying to recover from corrupted indexes, that could easily
+	 * cause a crash.)  We can accomplish this because CatalogUpdateIndexes
+	 * will use the relcache's index list to know which indexes to update.
+	 * We just force the index list to be only the stuff we've processed.
+	 *
+	 * It is okay to not insert entries into the indexes we have not
+	 * processed yet because all of this is transaction-safe.  If we fail
+	 * partway through, the updated rows are dead and it doesn't matter
+	 * whether they have index entries.  Also, a new pg_class index will
+	 * be created with an entry for its own pg_class row because we do
+	 * setNewRelfilenode() before we do index_build().
 	 */
-	if (rel->rd_rel->relisshared)
+	is_pg_class = (RelationGetRelid(rel) == RelOid_pg_class);
+	doneIndexes = NIL;
+
+	/* Reindex all the indexes. */
+	foreach(indexId, indexIds)
 	{
-		if (IsIgnoringSystemIndexes())
-		{
-			overwrite = true;
-			deactivate_needed = true;
-		}
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-					 errmsg("the target relation %u is shared", relid)));
-	}
+		Oid		indexOid = lfirsto(indexId);
 
-	old = SetReindexProcessing(true);
+		if (is_pg_class)
+			RelationSetIndexList(rel, doneIndexes);
 
-	if (deactivate_needed)
-	{
-		if (IndexesAreActive(rel))
-		{
-			if (!force)
-			{
-				SetReindexProcessing(old);
-				heap_close(rel, NoLock);
-				return false;
-			}
-			activate_indexes_of_a_table(rel, false);
-			CommandCounterIncrement();
-		}
+		reindex_index(indexOid);
+
+		CommandCounterIncrement();
+
+		if (is_pg_class)
+			doneIndexes = lappendo(doneIndexes, indexOid);
 	}
 
+	if (is_pg_class)
+		RelationSetIndexList(rel, indexIds);
+
 	/*
-	 * Continue to hold the lock.
+	 * Close rel, but continue to hold the lock.
 	 */
 	heap_close(rel, NoLock);
 
-	indexRelation = heap_openr(IndexRelationName, AccessShareLock);
-	ScanKeyEntryInitialize(&entry, 0, Anum_pg_index_indrelid,
-						   F_OIDEQ, ObjectIdGetDatum(relid));
-	scan = heap_beginscan(indexRelation, SnapshotNow, 1, &entry);
-	reindexed = false;
-	while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
-	{
-		Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
-
-		if (activate_index(index->indexrelid, true, overwrite))
-			reindexed = true;
-		else
-		{
-			reindexed = false;
-			break;
-		}
-	}
-	heap_endscan(scan);
-	heap_close(indexRelation, AccessShareLock);
-	if (reindexed)
-	{
-		/*
-		 * Ok,we could use the reindexed indexes of the target system
-		 * relation now.
-		 */
-		if (deactivate_needed)
-		{
-			if (!overwrite && relid == RelOid_pg_class)
-			{
-				/*
-				 * For pg_class, relhasindex should be set to true here in
-				 * place.
-				 */
-				setRelhasindex(relid, true, false, InvalidOid);
-				CommandCounterIncrement();
+	result = (indexIds != NIL);
 
-				/*
-				 * However the following setRelhasindex() is needed to
-				 * keep consistency with WAL.
-				 */
-			}
-			setRelhasindex(relid, true, false, InvalidOid);
-		}
-	}
-	SetReindexProcessing(old);
+	/*
+	 * If the relation has a secondary toast rel, reindex that too while we
+	 * still hold the lock on the master table.
+	 */
+	if (toast_relid != InvalidOid)
+		result |= reindex_relation(toast_relid);
 
-	return reindexed;
+	return result;
 }
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index 3bfe0ca23b9..85025671699 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_largeobject.c,v 1.16 2003/08/04 02:39:58 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_largeobject.c,v 1.17 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -77,32 +77,29 @@ LargeObjectDrop(Oid loid)
 {
 	bool		found = false;
 	Relation	pg_largeobject;
-	Relation	pg_lo_idx;
 	ScanKeyData skey[1];
-	IndexScanDesc sd;
+	SysScanDesc sd;
 	HeapTuple	tuple;
 
-	ScanKeyEntryInitialize(&skey[0],
-						   (bits16) 0x0,
-						   (AttrNumber) 1,
+	ScanKeyEntryInitialize(&skey[0], 0x0,
+						   (AttrNumber) Anum_pg_largeobject_loid,
 						   (RegProcedure) F_OIDEQ,
 						   ObjectIdGetDatum(loid));
 
-	pg_largeobject = heap_openr(LargeObjectRelationName, RowShareLock);
-	pg_lo_idx = index_openr(LargeObjectLOidPNIndex);
+	pg_largeobject = heap_openr(LargeObjectRelationName, RowExclusiveLock);
 
-	sd = index_beginscan(pg_largeobject, pg_lo_idx, SnapshotNow, 1, skey);
+	sd = systable_beginscan(pg_largeobject, LargeObjectLOidPNIndex, true,
+							SnapshotNow, 1, skey);
 
-	while ((tuple = index_getnext(sd, ForwardScanDirection)) != NULL)
+	while ((tuple = systable_getnext(sd)) != NULL)
 	{
 		simple_heap_delete(pg_largeobject, &tuple->t_self);
 		found = true;
 	}
 
-	index_endscan(sd);
+	systable_endscan(sd);
 
-	index_close(pg_lo_idx);
-	heap_close(pg_largeobject, RowShareLock);
+	heap_close(pg_largeobject, RowExclusiveLock);
 
 	if (!found)
 		ereport(ERROR,
@@ -115,32 +112,29 @@ LargeObjectExists(Oid loid)
 {
 	bool		retval = false;
 	Relation	pg_largeobject;
-	Relation	pg_lo_idx;
 	ScanKeyData skey[1];
-	IndexScanDesc sd;
+	SysScanDesc sd;
 	HeapTuple	tuple;
 
 	/*
 	 * See if we can find any tuples belonging to the specified LO
 	 */
-	ScanKeyEntryInitialize(&skey[0],
-						   (bits16) 0x0,
-						   (AttrNumber) 1,
+	ScanKeyEntryInitialize(&skey[0], 0x0,
+						   (AttrNumber) Anum_pg_largeobject_loid,
 						   (RegProcedure) F_OIDEQ,
 						   ObjectIdGetDatum(loid));
 
-	pg_largeobject = heap_openr(LargeObjectRelationName, RowShareLock);
-	pg_lo_idx = index_openr(LargeObjectLOidPNIndex);
+	pg_largeobject = heap_openr(LargeObjectRelationName, AccessShareLock);
 
-	sd = index_beginscan(pg_largeobject, pg_lo_idx, SnapshotNow, 1, skey);
+	sd = systable_beginscan(pg_largeobject, LargeObjectLOidPNIndex, true,
+							SnapshotNow, 1, skey);
 
-	if ((tuple = index_getnext(sd, ForwardScanDirection)) != NULL)
+	if ((tuple = systable_getnext(sd)) != NULL)
 		retval = true;
 
-	index_endscan(sd);
+	systable_endscan(sd);
 
-	index_close(pg_lo_idx);
-	heap_close(pg_largeobject, RowShareLock);
+	heap_close(pg_largeobject, AccessShareLock);
 
 	return retval;
 }
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index ea5ba103131..328643c171e 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/functioncmds.c,v 1.34 2003/09/10 19:59:23 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/functioncmds.c,v 1.35 2003/09/24 18:54:01 tgl Exp $
  *
  * DESCRIPTION
  *	  These routines take the parse tree and pick out the
@@ -1095,24 +1095,25 @@ DropCast(DropCastStmt *stmt)
 void
 DropCastById(Oid castOid)
 {
-	Relation	relation,
-				index;
+	Relation	relation;
 	ScanKeyData scankey;
-	IndexScanDesc scan;
+	SysScanDesc scan;
 	HeapTuple	tuple;
 
 	relation = heap_openr(CastRelationName, RowExclusiveLock);
-	index = index_openr(CastOidIndex);
 
 	ScanKeyEntryInitialize(&scankey, 0x0,
-						   1, F_OIDEQ, ObjectIdGetDatum(castOid));
-	scan = index_beginscan(relation, index, SnapshotNow, 1, &scankey);
-	tuple = index_getnext(scan, ForwardScanDirection);
+						   ObjectIdAttributeNumber,
+						   F_OIDEQ,
+						   ObjectIdGetDatum(castOid));
+	scan = systable_beginscan(relation, CastOidIndex, true,
+							  SnapshotNow, 1, &scankey);
+
+	tuple = systable_getnext(scan);
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "could not find tuple for cast %u", castOid);
 	simple_heap_delete(relation, &tuple->t_self);
-	index_endscan(scan);
 
-	index_close(index);
+	systable_endscan(scan);
 	heap_close(relation, RowExclusiveLock);
 }
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4c2221263d7..c8ccab8d4e1 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.108 2003/09/23 01:51:09 inoue Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.109 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -112,14 +112,6 @@ DefineIndex(RangeVar *heapRelation,
 	relationId = RelationGetRelid(rel);
 	namespaceId = RelationGetNamespace(rel);
 
-	if (!IsBootstrapProcessingMode() &&
-		IsSystemRelation(rel) &&
-		!IndexesAreActive(rel))
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEXES_DEACTIVATED),
-				 errmsg("existing indexes are inactive"),
-				 errhint("REINDEX the table first.")));
-
 	heap_close(rel, NoLock);
 
 	/*
@@ -599,10 +591,6 @@ ReindexIndex(RangeVar *indexRelation, bool force /* currently unused */ )
 {
 	Oid			indOid;
 	HeapTuple	tuple;
-	bool		overwrite;
-
-	/* Choose in-place-or-not mode */
-	overwrite = IsIgnoringSystemIndexes();
 
 	indOid = RangeVarGetRelid(indexRelation, false);
 	tuple = SearchSysCache(RELOID,
@@ -617,37 +605,14 @@ ReindexIndex(RangeVar *indexRelation, bool force /* currently unused */ )
 				 errmsg("relation \"%s\" is not an index",
 						indexRelation->relname)));
 
-	if (IsSystemClass((Form_pg_class) GETSTRUCT(tuple)) &&
-		!IsToastClass((Form_pg_class) GETSTRUCT(tuple)))
-	{
-		if (!allowSystemTableMods)
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("permission denied: \"%s\" is a system index",
-							indexRelation->relname),
-					 errhint("Do REINDEX in standalone postgres with -O -P options.")));
-		if (!IsIgnoringSystemIndexes())
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("permission denied: \"%s\" is a system index",
-							indexRelation->relname),
-					 errhint("Do REINDEX in standalone postgres with -P -O options.")));
-	}
+	/* Check permissions */
+	if (!pg_class_ownercheck(indOid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+					   indexRelation->relname);
 
 	ReleaseSysCache(tuple);
 
-	/*
-	 * In-place REINDEX within a transaction block is dangerous, because
-	 * if the transaction is later rolled back we have no way to undo
-	 * truncation of the index's physical file.  Disallow it.
-	 */
-	if (overwrite)
-		PreventTransactionChain((void *) indexRelation, "REINDEX");
-
-	if (!reindex_index(indOid, force, overwrite))
-		ereport(WARNING,
-				(errmsg("index \"%s\" wasn't reindexed",
-						indexRelation->relname)));
+	reindex_index(indOid);
 }
 
 /*
@@ -655,54 +620,62 @@ ReindexIndex(RangeVar *indexRelation, bool force /* currently unused */ )
  *		Recreate indexes of a table.
  */
 void
-ReindexTable(RangeVar *relation, bool force)
+ReindexTable(RangeVar *relation, bool force /* currently unused */ )
 {
 	Oid			heapOid;
-	char		relkind;
+	HeapTuple	tuple;
 
 	heapOid = RangeVarGetRelid(relation, false);
-	relkind = get_rel_relkind(heapOid);
+	tuple = SearchSysCache(RELOID,
+						   ObjectIdGetDatum(heapOid),
+						   0, 0, 0);
+	if (!HeapTupleIsValid(tuple))		/* shouldn't happen */
+		elog(ERROR, "cache lookup failed for relation %u", heapOid);
 
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE)
+	if (((Form_pg_class) GETSTRUCT(tuple))->relkind != RELKIND_RELATION &&
+		((Form_pg_class) GETSTRUCT(tuple))->relkind != RELKIND_TOASTVALUE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("relation \"%s\" is not a table",
 						relation->relname)));
 
-	/*
-	 * In-place REINDEX within a transaction block is dangerous, because
-	 * if the transaction is later rolled back we have no way to undo
-	 * truncation of the index's physical file.  Disallow it.
-	 *
-	 * XXX we assume that in-place reindex will only be done if
-	 * IsIgnoringSystemIndexes() is true.
-	 */
-	if (IsIgnoringSystemIndexes())
-		PreventTransactionChain((void *) relation, "REINDEX");
+	/* Check permissions */
+	if (!pg_class_ownercheck(heapOid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+					   relation->relname);
+
+	/* Can't reindex shared tables except in standalone mode */
+	if (((Form_pg_class) GETSTRUCT(tuple))->relisshared && IsUnderPostmaster)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("shared table \"%s\" can only be reindexed in standalone mode",
+						relation->relname)));
+
+	ReleaseSysCache(tuple);
 
-	if (!reindex_relation(heapOid, force))
+	if (!reindex_relation(heapOid))
 		ereport(WARNING,
-				(errmsg("table \"%s\" wasn't reindexed",
+				(errmsg("table \"%s\" has no indexes",
 						relation->relname)));
 }
 
 /*
  * ReindexDatabase
  *		Recreate indexes of a database.
+ *
+ * To reduce the probability of deadlocks, each table is reindexed in a
+ * separate transaction, so we can release the lock on it right away.
  */
 void
-ReindexDatabase(const char *dbname, bool force, bool all)
+ReindexDatabase(const char *dbname, bool force /* currently unused */,
+				bool all)
 {
 	Relation	relationRelation;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
 	MemoryContext private_context;
 	MemoryContext old;
-	int			relcnt,
-				relalc,
-				i,
-				oncealc = 200;
-	Oid		   *relids = (Oid *) NULL;
+	List	   *relids = NIL;
 
 	AssertArg(dbname);
 
@@ -715,21 +688,12 @@ ReindexDatabase(const char *dbname, bool force, bool all)
 		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
 					   dbname);
 
-	if (!allowSystemTableMods)
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("REINDEX DATABASE must be done in standalone postgres with -O -P options")));
-	if (!IsIgnoringSystemIndexes())
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("REINDEX DATABASE must be done in standalone postgres with -P -O options")));
-
 	/*
 	 * We cannot run inside a user transaction block; if we were inside a
 	 * transaction, then our commit- and start-transaction-command calls
 	 * would not have the intended effect!
 	 */
-	PreventTransactionChain((void *) dbname, "REINDEX");
+	PreventTransactionChain((void *) dbname, "REINDEX DATABASE");
 
 	/*
 	 * Create a memory context that will survive forced transaction
@@ -743,55 +707,68 @@ ReindexDatabase(const char *dbname, bool force, bool all)
 											ALLOCSET_DEFAULT_INITSIZE,
 											ALLOCSET_DEFAULT_MAXSIZE);
 
+	/*
+	 * We always want to reindex pg_class first.  This ensures that if
+	 * there is any corruption in pg_class' indexes, they will be fixed
+	 * before we process any other tables.  This is critical because
+	 * reindexing itself will try to update pg_class.
+	 */
+	old = MemoryContextSwitchTo(private_context);
+	relids = lappendo(relids, RelOid_pg_class);
+	MemoryContextSwitchTo(old);
+
 	/*
 	 * Scan pg_class to build a list of the relations we need to reindex.
+	 *
+	 * We only consider plain relations here (toast rels will be processed
+	 * indirectly by reindex_relation).
 	 */
 	relationRelation = heap_openr(RelationRelationName, AccessShareLock);
 	scan = heap_beginscan(relationRelation, SnapshotNow, 0, NULL);
-	relcnt = relalc = 0;
 	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
-		char		relkind;
+		Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
+
+		if (classtuple->relkind != RELKIND_RELATION)
+			continue;
 
-		if (!all)
+		if (!all)				/* only system tables? */
 		{
-			if (!(IsSystemClass((Form_pg_class) GETSTRUCT(tuple)) &&
-				  !IsToastClass((Form_pg_class) GETSTRUCT(tuple))))
+			if (!IsSystemClass(classtuple))
 				continue;
 		}
-		relkind = ((Form_pg_class) GETSTRUCT(tuple))->relkind;
-		if (relkind == RELKIND_RELATION || relkind == RELKIND_TOASTVALUE)
+
+		if (IsUnderPostmaster)	/* silently ignore shared tables */
 		{
-			old = MemoryContextSwitchTo(private_context);
-			if (relcnt == 0)
-			{
-				relalc = oncealc;
-				relids = palloc(sizeof(Oid) * relalc);
-			}
-			else if (relcnt >= relalc)
-			{
-				relalc *= 2;
-				relids = repalloc(relids, sizeof(Oid) * relalc);
-			}
-			MemoryContextSwitchTo(old);
-			relids[relcnt] = HeapTupleGetOid(tuple);
-			relcnt++;
+			if (classtuple->relisshared)
+				continue;
 		}
+
+		if (HeapTupleGetOid(tuple) == RelOid_pg_class)
+			continue;			/* got it already */
+
+		old = MemoryContextSwitchTo(private_context);
+		relids = lappendo(relids, HeapTupleGetOid(tuple));
+		MemoryContextSwitchTo(old);
 	}
 	heap_endscan(scan);
 	heap_close(relationRelation, AccessShareLock);
 
 	/* Now reindex each rel in a separate transaction */
 	CommitTransactionCommand();
-	for (i = 0; i < relcnt; i++)
+	while (relids)
 	{
+		Oid		relid = lfirsto(relids);
+
 		StartTransactionCommand();
 		SetQuerySnapshot();		/* might be needed for functions in
 								 * indexes */
-		if (reindex_relation(relids[i], force))
+		if (reindex_relation(relid))
 			ereport(NOTICE,
-					(errmsg("relation %u was reindexed", relids[i])));
+					(errmsg("table \"%s\" was reindexed",
+							get_rel_name(relid))));
 		CommitTransactionCommand();
+		relids = lnext(relids);
 	}
 	StartTransactionCommand();
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index f6331e8d214..e626848f12b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -13,7 +13,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.259 2003/08/04 02:39:58 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.260 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -904,11 +904,6 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
 	int			nindexes,
 				i;
 	VRelStats  *vacrelstats;
-	bool		reindex = false;
-
-	if (IsIgnoringSystemIndexes() &&
-		IsSystemRelation(onerel))
-		reindex = true;
 
 	vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared,
 						  &OldestXmin, &FreezeLimit);
@@ -927,27 +922,9 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
 
 	/* Now open all indexes of the relation */
 	vac_open_indexes(onerel, &nindexes, &Irel);
-	if (!Irel)
-		reindex = false;
-	else if (!RelationGetForm(onerel)->relhasindex)
-		reindex = true;
 	if (nindexes > 0)
 		vacrelstats->hasindex = true;
 
-#ifdef NOT_USED
-
-	/*
-	 * reindex in VACUUM is dangerous under WAL. ifdef out until it
-	 * becomes safe.
-	 */
-	if (reindex)
-	{
-		vac_close_indexes(nindexes, Irel);
-		Irel = (Relation *) NULL;
-		activate_indexes_of_a_table(onerel, false);
-	}
-#endif   /* NOT_USED */
-
 	/* Clean/scan index relation(s) */
 	if (Irel != (Relation *) NULL)
 	{
@@ -994,11 +971,6 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
 		}
 	}
 
-#ifdef NOT_USED
-	if (reindex)
-		activate_indexes_of_a_table(onerel, true);
-#endif   /* NOT_USED */
-
 	/* update shared free space map with final free space info */
 	vac_update_fsm(onerel, &fraged_pages, vacrelstats->rel_pages);
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9c815f84cda..4aa3170daa6 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.103 2003/08/08 21:41:40 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.104 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -647,12 +647,9 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo)
 
 	resultRelInfo->ri_NumIndices = 0;
 
-	/* checks for disabled indexes */
+	/* fast path if no indexes */
 	if (!RelationGetForm(resultRelation)->relhasindex)
 		return;
-	if (IsIgnoringSystemIndexes() &&
-		IsSystemRelation(resultRelation))
-		return;
 
 	/*
 	 * Get cached list of index OIDs
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index f93802269da..6ab2f0a47bd 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v 1.83 2003/08/22 20:26:43 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v 1.84 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -964,12 +964,6 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
 
 	currentRelation = heap_open(reloid, AccessShareLock);
 
-	if (!RelationGetForm(currentRelation)->relhasindex)
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("indexes of relation %u were deactivated",
-						reloid)));
-
 	indexstate->ss.ss_currentRelation = currentRelation;
 	indexstate->ss.ss_currentScanDesc = NULL;	/* no heap scan here */
 
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index edd12ed5c84..366a606684a 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.59 2003/08/04 02:40:03 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.60 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -73,6 +73,12 @@ SendSharedInvalidMessage(SharedInvalidationMessage *msg)
 /*
  * ReceiveSharedInvalidMessages
  *		Process shared-cache-invalidation messages waiting for this backend
+ *
+ * NOTE: it is entirely possible for this routine to be invoked recursively
+ * as a consequence of processing inside the invalFunction or resetFunction.
+ * Hence, we must be holding no SI resources when we call them.  The only
+ * bad side-effect is that SIDelExpiredDataEntries might be called extra
+ * times on the way out of a nested call.
  */
 void
 ReceiveSharedInvalidMessages(
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index c84d119ad78..e61c866b374 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.363 2003/09/14 00:03:32 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.364 2003/09/24 18:54:01 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -2252,9 +2252,12 @@ PostgresMain(int argc, char *argv[], const char *username)
 
 				/*
 				 * ignore system indexes
+				 *
+				 * As of PG 7.4 this is safe to allow from the client,
+				 * since it only disables reading the system indexes,
+				 * not writing them.  Worst case consequence is slowness.
 				 */
-				if (secure)		/* XXX safe to allow from client??? */
-					IgnoreSystemIndexes(true);
+				IgnoreSystemIndexes(true);
 				break;
 
 			case 'o':
@@ -2658,7 +2661,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 	if (!IsUnderPostmaster)
 	{
 		puts("\nPOSTGRES backend interactive interface ");
-		puts("$Revision: 1.363 $ $Date: 2003/09/14 00:03:32 $\n");
+		puts("$Revision: 1.364 $ $Date: 2003/09/24 18:54:01 $\n");
 	}
 
 	/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 629751e2f15..356948e12b8 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.206 2003/09/09 23:22:21 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.207 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -992,11 +992,9 @@ ProcessUtility(Node *parsetree,
 				switch (stmt->kind)
 				{
 					case OBJECT_INDEX:
-						CheckRelationOwnership(stmt->relation, false);
 						ReindexIndex(stmt->relation, stmt->force);
 						break;
 					case OBJECT_TABLE:
-						CheckRelationOwnership(stmt->relation, false);
 						ReindexTable(stmt->relation, stmt->force);
 						break;
 					case OBJECT_DATABASE:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 592a99faa7d..3c4cb46a74f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/relcache.c,v 1.188 2003/08/04 02:40:06 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/relcache.c,v 1.189 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -279,9 +279,7 @@ static HTAB *OpClassCache = NULL;
 
 static void RelationClearRelation(Relation relation, bool rebuild);
 
-#ifdef	ENABLE_REINDEX_NAILED_RELATIONS
 static void RelationReloadClassinfo(Relation relation);
-#endif   /* ENABLE_REINDEX_NAILED_RELATIONS */
 static void RelationFlushRelation(Relation relation);
 static Relation RelationSysNameCacheGetRelation(const char *relationName);
 static bool load_relcache_init_file(void);
@@ -290,7 +288,7 @@ static void write_relcache_init_file(void);
 static void formrdesc(const char *relationName, int natts,
 		  FormData_pg_attribute *att);
 
-static HeapTuple ScanPgRelation(RelationBuildDescInfo buildinfo);
+static HeapTuple ScanPgRelation(RelationBuildDescInfo buildinfo, bool indexOK);
 static Relation AllocateRelationDesc(Relation relation, Form_pg_class relp);
 static void RelationBuildTupleDesc(RelationBuildDescInfo buildinfo,
 					   Relation relation);
@@ -322,7 +320,7 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
  *		and must eventually be freed with heap_freetuple.
  */
 static HeapTuple
-ScanPgRelation(RelationBuildDescInfo buildinfo)
+ScanPgRelation(RelationBuildDescInfo buildinfo, bool indexOK)
 {
 	HeapTuple	pg_class_tuple;
 	Relation	pg_class_desc;
@@ -367,11 +365,12 @@ ScanPgRelation(RelationBuildDescInfo buildinfo)
 	/*
 	 * Open pg_class and fetch a tuple.  Force heap scan if we haven't yet
 	 * built the critical relcache entries (this includes initdb and
-	 * startup without a pg_internal.init file).
+	 * startup without a pg_internal.init file).  The caller can also
+	 * force a heap scan by setting indexOK == false.
 	 */
 	pg_class_desc = heap_openr(RelationRelationName, AccessShareLock);
 	pg_class_scan = systable_beginscan(pg_class_desc, indexRelname,
-									   criticalRelcachesBuilt,
+									   indexOK && criticalRelcachesBuilt,
 									   SnapshotNow,
 									   nkeys, key);
 
@@ -834,7 +833,7 @@ RelationBuildDesc(RelationBuildDescInfo buildinfo,
 	/*
 	 * find the tuple in pg_class corresponding to the given relation id
 	 */
-	pg_class_tuple = ScanPgRelation(buildinfo);
+	pg_class_tuple = ScanPgRelation(buildinfo, true);
 
 	/*
 	 * if no such tuple exists, return NULL
@@ -875,7 +874,7 @@ RelationBuildDesc(RelationBuildDescInfo buildinfo,
 	 * it could be new too, but it's okay to forget that fact if forced to
 	 * flush the entry.)
 	 */
-	relation->rd_isnailed = false;
+	relation->rd_isnailed = 0;
 	relation->rd_isnew = false;
 	relation->rd_istemp = isTempNamespace(relation->rd_rel->relnamespace);
 
@@ -1386,7 +1385,7 @@ formrdesc(const char *relationName,
 	 * all entries built with this routine are nailed-in-cache; none are
 	 * for new or temp relations.
 	 */
-	relation->rd_isnailed = true;
+	relation->rd_isnailed = 1;
 	relation->rd_isnew = false;
 	relation->rd_istemp = false;
 
@@ -1500,7 +1499,7 @@ formrdesc(const char *relationName,
  *		Lookup an existing reldesc by OID.
  *
  *		Only try to get the reldesc by looking in the cache,
- *		do not go to the disk.
+ *		do not go to the disk if it's not present.
  *
  *		NB: relation ref count is incremented if successful.
  *		Caller should eventually decrement count.  (Usually,
@@ -1514,7 +1513,12 @@ RelationIdCacheGetRelation(Oid relationId)
 	RelationIdCacheLookup(relationId, rd);
 
 	if (RelationIsValid(rd))
+	{
 		RelationIncrementReferenceCount(rd);
+		/* revalidate nailed index if necessary */
+		if (rd->rd_isnailed == 2)
+			RelationReloadClassinfo(rd);
+	}
 
 	return rd;
 }
@@ -1538,11 +1542,27 @@ RelationSysNameCacheGetRelation(const char *relationName)
 	RelationSysNameCacheLookup(NameStr(name), rd);
 
 	if (RelationIsValid(rd))
+	{
 		RelationIncrementReferenceCount(rd);
+		/* revalidate nailed index if necessary */
+		if (rd->rd_isnailed == 2)
+			RelationReloadClassinfo(rd);
+	}
 
 	return rd;
 }
 
+/*
+ *		RelationNodeCacheGetRelation
+ *
+ *		As above, but lookup by relfilenode.
+ *
+ * NOTE: this must NOT try to revalidate invalidated nailed indexes, since
+ * that could cause us to return an entry with a different relfilenode than
+ * the caller asked for.  Currently this is used only by the buffer manager.
+ * Really the bufmgr's idea of relations should be separated out from the
+ * relcache ...
+ */
 Relation
 RelationNodeCacheGetRelation(RelFileNode rnode)
 {
@@ -1647,39 +1667,60 @@ RelationClose(Relation relation)
 #endif
 }
 
-#ifdef	ENABLE_REINDEX_NAILED_RELATIONS
 /*
- * RelationReloadClassinfo
- *
- *	This function is especially for nailed relations.
- *	relhasindex/relfilenode could be changed even for
- *	nailed relations.
+ * RelationReloadClassinfo - reload the pg_class row (only)
+ *
+ *	This function is used only for nailed indexes.  Since a REINDEX can
+ *	change the relfilenode value for a nailed index, we have to reread
+ *	the pg_class row anytime we get an SI invalidation on a nailed index
+ *	(without throwing away the whole relcache entry, since we'd be unable
+ *	to rebuild it).
+ *
+ *	We can't necessarily reread the pg_class row right away; we might be
+ *	in a failed transaction when we receive the SI notification.  If so,
+ *	RelationClearRelation just marks the entry as invalid by setting
+ *	rd_isnailed to 2.  This routine is called to fix the entry when it
+ *	is next needed.
  */
 static void
 RelationReloadClassinfo(Relation relation)
 {
 	RelationBuildDescInfo buildinfo;
+	bool		indexOK;
 	HeapTuple	pg_class_tuple;
 	Form_pg_class relp;
 
-	if (!relation->rd_rel)
-		return;
+	/* Should be called only for invalidated nailed indexes */
+	Assert(relation->rd_isnailed == 2 &&
+		   relation->rd_rel->relkind == RELKIND_INDEX);
+	/* Read the pg_class row */
 	buildinfo.infotype = INFO_RELID;
 	buildinfo.i.info_id = relation->rd_id;
-	pg_class_tuple = ScanPgRelation(buildinfo);
+	/*
+	 * Don't try to use an indexscan of pg_class_oid_index to reload the
+	 * info for pg_class_oid_index ...
+	 */
+	indexOK = strcmp(RelationGetRelationName(relation), ClassOidIndex) != 0;
+	pg_class_tuple = ScanPgRelation(buildinfo, indexOK);
 	if (!HeapTupleIsValid(pg_class_tuple))
 		elog(ERROR, "could not find tuple for system relation %u",
 			 relation->rd_id);
-	RelationCacheDelete(relation);
 	relp = (Form_pg_class) GETSTRUCT(pg_class_tuple);
-	memcpy((char *) relation->rd_rel, (char *) relp, CLASS_TUPLE_SIZE);
-	relation->rd_node.relNode = relp->relfilenode;
-	RelationCacheInsert(relation);
+	if (relation->rd_node.relNode != relp->relfilenode)
+	{
+		/* We have to re-insert the entry into the relcache indexes */
+		RelationCacheDelete(relation);
+		memcpy((char *) relation->rd_rel, (char *) relp, CLASS_TUPLE_SIZE);
+		relation->rd_node.relNode = relp->relfilenode;
+		RelationCacheInsert(relation);
+	}
 	heap_freetuple(pg_class_tuple);
-
-	return;
+	/* Must adjust number of blocks after we know the new relfilenode */
+	relation->rd_targblock = InvalidBlockNumber;
+	RelationUpdateNumberOfBlocks(relation);
+	/* Okay, now it's valid again */
+	relation->rd_isnailed = 1;
 }
-#endif   /* ENABLE_REINDEX_NAILED_RELATIONS */
 
 /*
  * RelationClearRelation
@@ -1712,15 +1753,27 @@ RelationClearRelation(Relation relation, bool rebuild)
 	 * Never, never ever blow away a nailed-in system relation, because
 	 * we'd be unable to recover.  However, we must update rd_nblocks and
 	 * reset rd_targblock, in case we got called because of a relation
-	 * cache flush that was triggered by VACUUM.
+	 * cache flush that was triggered by VACUUM.  If it's a nailed index,
+	 * then we need to re-read the pg_class row to see if its relfilenode
+	 * changed.  We can't necessarily do that here, because we might be in
+	 * a failed transaction.  We assume it's okay to do it if there are open
+	 * references to the relcache entry (cf notes for AtEOXact_RelationCache).
+	 * Otherwise just mark the entry as possibly invalid, and it'll be fixed
+	 * when next opened.
 	 */
 	if (relation->rd_isnailed)
 	{
-		relation->rd_targblock = InvalidBlockNumber;
-		RelationUpdateNumberOfBlocks(relation);
-#ifdef	ENABLE_REINDEX_NAILED_RELATIONS
-		RelationReloadClassinfo(relation);
-#endif   /* ENABLE_REINDEX_NAILED_RELATIONS */
+		if (relation->rd_rel->relkind == RELKIND_INDEX)
+		{
+			relation->rd_isnailed = 2;	/* needs to be revalidated */
+			if (relation->rd_refcnt > 1)
+				RelationReloadClassinfo(relation);
+		}
+		else
+		{
+			relation->rd_targblock = InvalidBlockNumber;
+			RelationUpdateNumberOfBlocks(relation);
+		}
 		return;
 	}
 
@@ -1928,6 +1981,12 @@ RelationIdInvalidateRelationCacheByRelationId(Oid relationId)
  *	 because (a) during the first pass we won't process any more SI messages,
  *	 so hash_seq_search will complete safely; (b) during the second pass we
  *	 only hold onto pointers to nondeletable entries.
+ *
+ *	 The two-phase approach also makes it easy to ensure that we process
+ *	 nailed-in-cache indexes before other nondeletable items, and that we
+ *	 process pg_class_oid_index first of all.  In scenarios where a nailed
+ *	 index has been given a new relfilenode, we have to detect that update
+ *	 before the nailed index is used in reloading any other relcache entry.
  */
 void
 RelationCacheInvalidate(void)
@@ -1935,6 +1994,7 @@ RelationCacheInvalidate(void)
 	HASH_SEQ_STATUS status;
 	RelIdCacheEnt *idhentry;
 	Relation	relation;
+	List	   *rebuildFirstList = NIL;
 	List	   *rebuildList = NIL;
 	List	   *l;
 
@@ -1954,15 +2014,33 @@ RelationCacheInvalidate(void)
 		if (RelationHasReferenceCountZero(relation))
 		{
 			/* Delete this entry immediately */
+			Assert(!relation->rd_isnailed);
 			RelationClearRelation(relation, false);
 		}
 		else
 		{
-			/* Add entry to list of stuff to rebuild in second pass */
-			rebuildList = lcons(relation, rebuildList);
+			/*
+			 * Add this entry to list of stuff to rebuild in second pass.
+			 * pg_class_oid_index goes on the front of rebuildFirstList,
+			 * other nailed indexes on the back, and everything else into
+			 * rebuildList (in no particular order).
+			 */
+			if (relation->rd_isnailed &&
+				relation->rd_rel->relkind == RELKIND_INDEX)
+			{
+				if (strcmp(RelationGetRelationName(relation),
+						   ClassOidIndex) == 0)
+					rebuildFirstList = lcons(relation, rebuildFirstList);
+				else
+					rebuildFirstList = lappend(rebuildFirstList, relation);
+			}
+			else
+				rebuildList = lcons(relation, rebuildList);
 		}
 	}
 
+	rebuildList = nconc(rebuildFirstList, rebuildList);
+
 	/* Phase 2: rebuild the items found to need rebuild in phase 1 */
 	foreach(l, rebuildList)
 	{
@@ -1976,6 +2054,11 @@ RelationCacheInvalidate(void)
  * AtEOXact_RelationCache
  *
  *	Clean up the relcache at transaction commit or abort.
+ *
+ * Note: this must be called *before* processing invalidation messages.
+ * In the case of abort, we don't want to try to rebuild any invalidated
+ * cache entries (since we can't safely do database accesses).  Therefore
+ * we must reset refcnts before handling pending invalidations.
  */
 void
 AtEOXact_RelationCache(bool commit)
@@ -2045,6 +2128,16 @@ AtEOXact_RelationCache(bool commit)
 			/* abort case, just reset it quietly */
 			RelationSetReferenceCount(relation, expected_refcnt);
 		}
+
+		/*
+		 * Flush any temporary index list.
+		 */
+		if (relation->rd_indexvalid == 2)
+		{
+			freeList(relation->rd_indexlist);
+			relation->rd_indexlist = NIL;
+			relation->rd_indexvalid = 0;
+		}
 	}
 }
 
@@ -2101,7 +2194,7 @@ RelationBuildLocalRelation(const char *relname,
 	 * want it kicked out.	e.g. pg_attribute!!!
 	 */
 	if (nailit)
-		rel->rd_isnailed = true;
+		rel->rd_isnailed = 1;
 
 	/*
 	 * create a new tuple descriptor from the one passed in.  We do this
@@ -2288,7 +2381,7 @@ RelationCacheInitializePhase2(void)
 			buildinfo.infotype = INFO_RELNAME; \
 			buildinfo.i.info_name = (indname); \
 			ird = RelationBuildDesc(buildinfo, NULL); \
-			ird->rd_isnailed = true; \
+			ird->rd_isnailed = 1; \
 			RelationSetReferenceCount(ird, 1); \
 		} while (0)
 
@@ -2575,7 +2668,7 @@ CheckConstraintFetch(Relation relation)
  * The index list is created only if someone requests it.  We scan pg_index
  * to find relevant indexes, and add the list to the relcache entry so that
  * we won't have to compute it again.  Note that shared cache inval of a
- * relcache entry will delete the old list and set rd_indexfound to false,
+ * relcache entry will delete the old list and set rd_indexvalid to 0,
  * so that we must recompute the index list on next request.  This handles
  * creation or deletion of an index.
  *
@@ -2602,7 +2695,7 @@ RelationGetIndexList(Relation relation)
 	MemoryContext oldcxt;
 
 	/* Quick exit if we already computed the list. */
-	if (relation->rd_indexfound)
+	if (relation->rd_indexvalid != 0)
 		return listCopy(relation->rd_indexlist);
 
 	/*
@@ -2638,7 +2731,7 @@ RelationGetIndexList(Relation relation)
 	/* Now save a copy of the completed list in the relcache entry. */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	relation->rd_indexlist = listCopy(result);
-	relation->rd_indexfound = true;
+	relation->rd_indexvalid = 1;
 	MemoryContextSwitchTo(oldcxt);
 
 	return result;
@@ -2676,6 +2769,35 @@ insert_ordered_oid(List *list, Oid datum)
 	return list;
 }
 
+/*
+ * RelationSetIndexList -- externally force the index list contents
+ *
+ * This is used to temporarily override what we think the set of valid
+ * indexes is.  The forcing will be valid only until transaction commit
+ * or abort.
+ *
+ * This should only be applied to nailed relations, because in a non-nailed
+ * relation the hacked index list could be lost at any time due to SI
+ * messages.  In practice it is only used on pg_class (see REINDEX).
+ *
+ * It is up to the caller to make sure the given list is correctly ordered.
+ */
+void
+RelationSetIndexList(Relation relation, List *indexIds)
+{
+	MemoryContext oldcxt;
+
+	Assert(relation->rd_isnailed == 1);
+	/* Copy the list into the cache context (could fail for lack of mem) */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	indexIds = listCopy(indexIds);
+	MemoryContextSwitchTo(oldcxt);
+	/* Okay to replace old list */
+	freeList(relation->rd_indexlist);
+	relation->rd_indexlist = indexIds;
+	relation->rd_indexvalid = 2;		/* mark list as forced */
+}
+
 /*
  * RelationGetIndexExpressions -- get the index expressions for an index
  *
@@ -3087,7 +3209,7 @@ load_relcache_init_file(void)
 			RelationSetReferenceCount(rel, 1);
 		else
 			RelationSetReferenceCount(rel, 0);
-		rel->rd_indexfound = false;
+		rel->rd_indexvalid = 0;
 		rel->rd_indexlist = NIL;
 		MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info));
 
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index aa89bb81787..9b250eef62e 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/syscache.c,v 1.90 2003/08/04 02:40:06 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/syscache.c,v 1.91 2003/09/24 18:54:01 tgl Exp $
  *
  * NOTES
  *	  These routines allow the parser/planner/executor to perform
@@ -436,19 +436,11 @@ static const struct cachedesc cacheinfo[] = {
 	}}
 };
 
-static CatCache *SysCache[
-						  lengthof(cacheinfo)];
+static CatCache *SysCache[lengthof(cacheinfo)];
 static int	SysCacheSize = lengthof(cacheinfo);
 static bool CacheInitialized = false;
 
 
-bool
-IsCacheInitialized(void)
-{
-	return CacheInitialized;
-}
-
-
 /*
  * InitCatalogCache - initialize the caches
  *
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 9f19d1187ba..22baac3706f 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/init/miscinit.c,v 1.113 2003/08/04 04:03:10 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/init/miscinit.c,v 1.114 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,6 +51,11 @@ static char socketLockFile[MAXPGPATH];
 
 /* ----------------------------------------------------------------
  *		ignoring system indexes support stuff
+ *
+ * NOTE: "ignoring system indexes" means we do not use the system indexes
+ * for lookups (either in hardwired catalog accesses or in planner-generated
+ * plans).  We do, however, still update the indexes when a catalog
+ * modification is made.
  * ----------------------------------------------------------------
  */
 
@@ -61,15 +66,14 @@ static bool isIgnoringSystemIndexes = false;
  *		True if ignoring system indexes.
  */
 bool
-IsIgnoringSystemIndexes()
+IsIgnoringSystemIndexes(void)
 {
 	return isIgnoringSystemIndexes;
 }
 
 /*
  * IgnoreSystemIndexes
- *	Set true or false whether PostgreSQL ignores system indexes.
- *
+ *		Set true or false whether PostgreSQL ignores system indexes.
  */
 void
 IgnoreSystemIndexes(bool mode)
@@ -77,6 +81,53 @@ IgnoreSystemIndexes(bool mode)
 	isIgnoringSystemIndexes = mode;
 }
 
+/* ----------------------------------------------------------------
+ *		system index reindexing support
+ *
+ * When we are busy reindexing a system index, this code provides support
+ * for preventing catalog lookups from using that index.
+ * ----------------------------------------------------------------
+ */
+
+static Oid	currentlyReindexedHeap = InvalidOid;
+static Oid	currentlyReindexedIndex = InvalidOid;
+
+/*
+ * ReindexIsProcessingHeap
+ *		True if heap specified by OID is currently being reindexed.
+ */
+bool
+ReindexIsProcessingHeap(Oid heapOid)
+{
+	return heapOid == currentlyReindexedHeap;
+}
+
+/*
+ * ReindexIsProcessingIndex
+ *		True if index specified by OID is currently being reindexed.
+ */
+bool
+ReindexIsProcessingIndex(Oid indexOid)
+{
+	return indexOid == currentlyReindexedIndex;
+}
+
+/*
+ * SetReindexProcessing
+ *		Set flag that specified heap/index are being reindexed.
+ *		Pass InvalidOid to indicate that reindexing is not active.
+ */
+void
+SetReindexProcessing(Oid heapOid, Oid indexOid)
+{
+	/* Args should be both, or neither, InvalidOid */
+	Assert((heapOid == InvalidOid) == (indexOid == InvalidOid));
+	/* Reindexing is not re-entrant. */
+	Assert(indexOid == InvalidOid || currentlyReindexedIndex == InvalidOid);
+	currentlyReindexedHeap = heapOid;
+	currentlyReindexedIndex = indexOid;
+}
+
 /* ----------------------------------------------------------------
  *				database path / name support stuff
  * ----------------------------------------------------------------
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index e3ee98a7c38..2a45c290b92 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: index.h,v 1.52 2003/08/04 02:40:10 momjian Exp $
+ * $Id: index.h,v 1.53 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,15 +51,12 @@ extern void FormIndexDatum(IndexInfo *indexInfo,
 			   char *nullv);
 
 extern void UpdateStats(Oid relid, double reltuples);
-extern bool IndexesAreActive(Relation heaprel);
+
 extern void setRelhasindex(Oid relid, bool hasindex,
 			   bool isprimary, Oid reltoastidxid);
 
 extern void setNewRelfilenode(Relation relation);
 
-extern bool SetReindexProcessing(bool processing);
-extern bool IsReindexProcessing(void);
-
 extern void index_build(Relation heapRelation, Relation indexRelation,
 			IndexInfo *indexInfo);
 
@@ -69,9 +66,7 @@ extern double IndexBuildHeapScan(Relation heapRelation,
 				   IndexBuildCallback callback,
 				   void *callback_state);
 
-extern bool activate_indexes_of_a_table(Relation heaprel, bool activate);
-
-extern bool reindex_index(Oid indexId, bool force, bool inplace);
-extern bool reindex_relation(Oid relid, bool force);
+extern void reindex_index(Oid indexId);
+extern bool reindex_relation(Oid relid);
 
 #endif   /* INDEX_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index d5d6d85c62a..1c1c1d34519 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: miscadmin.h,v 1.133 2003/08/26 15:38:25 tgl Exp $
+ * $Id: miscadmin.h,v 1.134 2003/09/24 18:54:01 tgl Exp $
  *
  * NOTES
  *	  some of the information in this file should be moved to
@@ -296,18 +296,17 @@ extern void InitPostgres(const char *dbname, const char *username);
 extern void BaseInit(void);
 
 /* in utils/init/miscinit.c */
+extern void IgnoreSystemIndexes(bool mode);
+extern bool IsIgnoringSystemIndexes(void);
+extern void SetReindexProcessing(Oid heapOid, Oid indexOid);
+extern bool ReindexIsProcessingHeap(Oid heapOid);
+extern bool ReindexIsProcessingIndex(Oid indexOid);
 extern void CreateDataDirLockFile(const char *datadir, bool amPostmaster);
 extern void CreateSocketLockFile(const char *socketfile, bool amPostmaster);
 extern void TouchSocketLockFile(void);
 extern void RecordSharedMemoryInLockFile(unsigned long id1,
 							 unsigned long id2);
-
 extern void ValidatePgVersion(const char *path);
 extern void process_preload_libraries(char *preload_libraries_string);
 
-/* these externs do not belong here... */
-extern void IgnoreSystemIndexes(bool mode);
-extern bool IsIgnoringSystemIndexes(void);
-extern bool IsCacheInitialized(void);
-
 #endif   /* MISCADMIN_H */
diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h
index 13e42cf1b9d..fa1cd254a4f 100644
--- a/src/include/utils/errcodes.h
+++ b/src/include/utils/errcodes.h
@@ -11,7 +11,7 @@
  *
  * Copyright (c) 2003, PostgreSQL Global Development Group
  *
- * $Id: errcodes.h,v 1.5 2003/08/26 21:15:27 tgl Exp $
+ * $Id: errcodes.h,v 1.6 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -281,7 +281,6 @@
 /* Class 55 - Object Not In Prerequisite State (class borrowed from DB2) */
 #define ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE	MAKE_SQLSTATE('5','5', '0','0','0')
 #define ERRCODE_OBJECT_IN_USE				MAKE_SQLSTATE('5','5', '0','0','6')
-#define ERRCODE_INDEXES_DEACTIVATED			MAKE_SQLSTATE('5','5', 'P','0','1')
 #define ERRCODE_CANT_CHANGE_RUNTIME_PARAM	MAKE_SQLSTATE('5','5', 'P','0','2')
 
 /* Class 57 - Operator Intervention (class borrowed from DB2) */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0971abf000a..377822afc69 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: rel.h,v 1.67 2003/08/04 02:40:15 momjian Exp $
+ * $Id: rel.h,v 1.68 2003/09/24 18:54:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -119,8 +119,10 @@ typedef struct RelationData
 	 * it is possible for new-ness to be "forgotten" (eg, after CLUSTER).
 	 */
 	bool		rd_istemp;		/* rel uses the local buffer mgr */
-	bool		rd_isnailed;	/* rel is nailed in cache */
-	bool		rd_indexfound;	/* true if rd_indexlist is valid */
+	char		rd_isnailed;	/* rel is nailed in cache: 0 = no, 1 = yes,
+								 * 2 = yes but possibly invalid */
+	char		rd_indexvalid;	/* state of rd_indexlist: 0 = not valid,
+								 * 1 = valid, 2 = temporarily forced */
 	Form_pg_class rd_rel;		/* RELATION tuple */
 	TupleDesc	rd_att;			/* tuple descriptor */
 	Oid			rd_id;			/* relation's object id */
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 5d08e9b0256..61e09c38461 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: relcache.h,v 1.36 2003/08/04 02:40:15 momjian Exp $
+ * $Id: relcache.h,v 1.37 2003/09/24 18:54:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -35,6 +35,8 @@ extern List *RelationGetIndexList(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
 
+extern void RelationSetIndexList(Relation relation, List *indexIds);
+
 extern void RelationInitIndexAccessInfo(Relation relation);
 
 /*
-- 
GitLab