diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index e2c3139d8534237bd9d13f8452f8942f1b74fc36..39c05e24bd8193056af42341e3b4646d0412cb69 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1,6 +1,6 @@
 <!--
  Documentation of the system catalogs, directed toward PostgreSQL developers
- $Header: /cvsroot/pgsql/doc/src/sgml/catalogs.sgml,v 2.21 2001/08/21 16:35:58 tgl Exp $
+ $Header: /cvsroot/pgsql/doc/src/sgml/catalogs.sgml,v 2.22 2001/08/26 16:55:58 tgl Exp $
  -->
 
 <chapter id="catalogs">
@@ -840,11 +840,34 @@
       <entry><type>oid</type></entry>
       <entry></entry>
       <entry>
-       Last oid in existence after the database was created; useful
+       Last system OID in the database; useful
        particularly to <application>pg_dump</application>
       </entry>
      </row>
 
+     <row>
+      <entry>datvacuumxid</entry>
+      <entry><type>xid</type></entry>
+      <entry></entry>
+      <entry>
+       All tuples inserted or deleted by transaction IDs before this one
+       have been marked as known committed or known aborted in this database.
+       This is used to determine when commit-log space can be recycled.
+      </entry>
+     </row>
+
+     <row>
+      <entry>datfrozenxid</entry>
+      <entry><type>xid</type></entry>
+      <entry></entry>
+      <entry>
+       All tuples inserted by transaction IDs before this one have been
+       relabeled with a permanent (<quote>frozen</>) transaction ID in this
+       database.  This is useful to check whether a database must be vacuumed
+       soon to avoid transaction ID wraparound problems.
+      </entry>
+     </row>
+
      <row>
       <entry>datpath</entry>
       <entry><type>text</type></entry>
diff --git a/doc/src/sgml/ref/create_database.sgml b/doc/src/sgml/ref/create_database.sgml
index e128c1891d0f097cadee4d51da2859ce196d2495..20477ac191c269038f39352cdd2f296b2b0deb9f 100644
--- a/doc/src/sgml/ref/create_database.sgml
+++ b/doc/src/sgml/ref/create_database.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_database.sgml,v 1.17 2000/11/15 19:43:39 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_database.sgml,v 1.18 2001/08/26 16:55:59 tgl Exp $
 Postgres documentation
 -->
 
@@ -284,6 +284,20 @@ comment from Olly; response from Thomas...
    simply by setting the flag false).  The <literal>template0</literal>
    database is normally marked this way to prevent modification of it.
   </para>
+
+  <para>
+   After preparing a template database, or making any changes to one,
+   it is a good idea to perform
+   <command>VACUUM FREEZE</> or <command>VACUUM FULL FREEZE</> in that
+   database.  If this is done when there are no other open transactions
+   in the same database, then it is guaranteed that all tuples in the
+   database are <quote>frozen</> and will not be subject to transaction
+   ID wraparound problems.  This is particularly important for a database
+   that will have <literal>datallowconn</literal> set to false, since it
+   will be impossible to do routine maintenance <command>VACUUM</>s on
+   such a database.
+    See the Administrator's Guide for more information.
+  </para>
   </refsect2>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index e8374725b34bd3cc8e98d224ca092a866bd6f5ec..62e47a7086dc2f9e0a2b18a36317b94d9e07c7da 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/vacuum.sgml,v 1.17 2001/07/10 22:09:28 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/vacuum.sgml,v 1.18 2001/08/26 16:55:59 tgl Exp $
 Postgres documentation
 -->
 
@@ -20,11 +20,11 @@ Postgres documentation
  </refnamediv>
  <refsynopsisdiv>
   <refsynopsisdivinfo>
-   <date>2001-07-10</date>
+   <date>2001-08-26</date>
   </refsynopsisdivinfo>
   <synopsis>
-VACUUM [ FULL ] [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> ]
-VACUUM [ FULL ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
   </synopsis>
 
   <refsect2 id="R2-SQL-VACUUM-1">
@@ -46,6 +46,14 @@ VACUUM [ FULL ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table</repl
        </para>
       </listitem>
      </varlistentry>
+     <varlistentry>
+      <term>FREEZE</term>
+      <listitem>
+       <para>
+        Selects aggressive <quote>freezing</quote> of tuples.
+       </para>
+      </listitem>
+     </varlistentry>
      <varlistentry>
       <term>VERBOSE</term>
       <listitem>
@@ -169,21 +177,38 @@ NOTICE:  Index <replaceable class="PARAMETER">index</replaceable>: Pages 28;
   </para>
 
   <para>
-   Plain <command>VACUUM</command> simply reclaims space and makes it
+   <command>VACUUM ANALYZE</command> performs a <command>VACUUM</command>
+   and then an <command>ANALYZE</command> for each selected table.  This
+   is a handy combination form for routine maintenance scripts.  See
+   <xref linkend="sql-analyze" endterm="sql-analyze-title">
+   for more details about its processing.
+  </para>
+
+  <para>
+   Plain <command>VACUUM</command> (without <literal>FULL</>) simply reclaims
+   space and makes it
    available for re-use.  This form of the command can operate in parallel
    with normal reading and writing of the table.  <command>VACUUM
    FULL</command> does more extensive processing, including moving of tuples
    across blocks to try to compact the table to the minimum number of disk
-   blocks.  This is much slower and requires an exclusive lock on each table
-   while it is being processed.
+   blocks.  This form is much slower and requires an exclusive lock on each
+   table while it is being processed.
   </para>
 
   <para>
-   <command>VACUUM ANALYZE</command> performs a <command>VACUUM</command>
-   and then an <command>ANALYZE</command> for each selected table.  This
-   is a handy combination form for routine maintenance scripts.  See
-   <xref linkend="sql-analyze" endterm="sql-analyze-title">
-   for more details about its processing.
+   <command>FREEZE</command> is a special-purpose option that
+   causes tuples to be marked <quote>frozen</quote> as soon as possible,
+   rather than waiting until they are quite old.  If this is done when there
+   are no other open transactions in the same database, then it is guaranteed
+   that all tuples in the database are <quote>frozen</> and will not be
+   subject to transaction ID wraparound problems, no matter how long the
+   database is left un-vacuumed.
+   <command>FREEZE</command> is not recommended for routine use.  Its only
+   intended usage is in connection with preparation of user-defined template
+   databases, or other databases that are completely read-only and will not
+   receive routine maintenance <command>VACUUM</> operations.
+   See <xref linkend="sql-createdatabase" endterm="sql-createdatabase-title">
+   for details.
   </para>
 
   <refsect2 id="R2-SQL-VACUUM-3">
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 07581e4444a875e08f562e9f89bdd49fff966135..a403838bd792ec086f1a517db5bea8b3604728f7 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/access/transam/clog.c,v 1.2 2001/08/25 23:24:39 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/access/transam/clog.c,v 1.3 2001/08/26 16:55:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -762,8 +762,12 @@ ExtendCLOG(TransactionId newestXact)
 {
 	int			pageno;
 
-	/* No work except at first XID of a page */
-	if (TransactionIdToPgIndex(newestXact) != 0)
+	/*
+	 * No work except at first XID of a page.  But beware: just after
+	 * wraparound, the first XID of page zero is FirstNormalTransactionId.
+	 */
+	if (TransactionIdToPgIndex(newestXact) != 0 &&
+		!TransactionIdEquals(newestXact, FirstNormalTransactionId))
 		return;
 
 	pageno = TransactionIdToPage(newestXact);
@@ -818,6 +822,18 @@ TruncateCLOG(TransactionId oldestXact)
 	S_LOCK(&(ClogCtl->control_lck));
 
 restart:;
+	/*
+	 * While we are holding the lock, make an important safety check:
+	 * the planned cutoff point must be <= the current CLOG endpoint page.
+	 * Otherwise we have already wrapped around, and proceeding with the
+	 * truncation would risk removing the current CLOG segment.
+	 */
+	if (CLOGPagePrecedes(ClogCtl->latest_page_number, cutoffPage))
+	{
+		S_UNLOCK(&(ClogCtl->control_lck));
+		elog(LOG, "unable to truncate commit log: apparent wraparound");
+		return;
+	}
 
 	for (slotno = 0; slotno < NUM_CLOG_BUFFERS; slotno++)
 	{
diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c
index 3364ed66337b9b709defd09c31b2d45f1cb8a066..2a73c045b76ad069ac8940fc4cf56f1fcff85b78 100644
--- a/src/backend/access/transam/transam.c
+++ b/src/backend/access/transam/transam.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/transam/transam.c,v 1.47 2001/08/25 18:52:41 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/transam/transam.c,v 1.48 2001/08/26 16:55:59 tgl Exp $
  *
  * NOTES
  *	  This file contains the high level access-method interface to the
@@ -236,3 +236,68 @@ TransactionIdAbort(TransactionId transactionId)
 
 	TransactionLogUpdate(transactionId, TRANSACTION_STATUS_ABORTED);
 }
+
+
+/*
+ * TransactionIdPrecedes --- is id1 logically < id2?
+ */
+bool
+TransactionIdPrecedes(TransactionId id1, TransactionId id2)
+{
+	/*
+	 * If either ID is a permanent XID then we can just do unsigned
+	 * comparison.  If both are normal, do a modulo-2^31 comparison.
+	 */
+	int32		diff;
+
+	if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
+		return (id1 < id2);
+
+	diff = (int32) (id1 - id2);
+	return (diff < 0);
+}
+
+/*
+ * TransactionIdPrecedesOrEquals --- is id1 logically <= id2?
+ */
+bool
+TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2)
+{
+	int32		diff;
+
+	if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
+		return (id1 <= id2);
+
+	diff = (int32) (id1 - id2);
+	return (diff <= 0);
+}
+
+/*
+ * TransactionIdFollows --- is id1 logically > id2?
+ */
+bool
+TransactionIdFollows(TransactionId id1, TransactionId id2)
+{
+	int32		diff;
+
+	if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
+		return (id1 > id2);
+
+	diff = (int32) (id1 - id2);
+	return (diff > 0);
+}
+
+/*
+ * TransactionIdFollowsOrEquals --- is id1 logically >= id2?
+ */
+bool
+TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2)
+{
+	int32		diff;
+
+	if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
+		return (id1 >= id2);
+
+	diff = (int32) (id1 - id2);
+	return (diff >= 0);
+}
diff --git a/src/backend/access/transam/xid.c b/src/backend/access/transam/xid.c
index 689fc33ceaff5d65edec367f860e1a29af5ee372..babe7c9a48bfcad032e480ca8a3f0446dfa2fb8b 100644
--- a/src/backend/access/transam/xid.c
+++ b/src/backend/access/transam/xid.c
@@ -6,23 +6,18 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Id: xid.c,v 1.32 2001/08/23 23:06:37 tgl Exp $
- *
- * OLD COMMENTS
- * XXX WARNING
- *		Much of this file will change when we change our representation
- *		of transaction ids -cim 3/23/90
- *
- * It is time to make the switch from 5 byte to 4 byte transaction ids
- * This file was totally reworked. -mer 5/22/92
+ *	$Id: xid.c,v 1.33 2001/08/26 16:55:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include <limits.h>
+
 #include "access/xact.h"
 
+
 #define PG_GETARG_TRANSACTIONID(n)	DatumGetTransactionId(PG_GETARG_DATUM(n))
 #define PG_RETURN_TRANSACTIONID(x)	return TransactionIdGetDatum(x)
 
@@ -30,9 +25,9 @@
 Datum
 xidin(PG_FUNCTION_ARGS)
 {
-	char	   *representation = PG_GETARG_CSTRING(0);
+	char	   *str = PG_GETARG_CSTRING(0);
 
-	PG_RETURN_TRANSACTIONID((TransactionId) atol(representation));
+	PG_RETURN_TRANSACTIONID((TransactionId) strtoul(str, NULL, 0));
 }
 
 Datum
@@ -40,21 +35,15 @@ xidout(PG_FUNCTION_ARGS)
 {
 	TransactionId transactionId = PG_GETARG_TRANSACTIONID(0);
 	/* maximum 32 bit unsigned integer representation takes 10 chars */
-	char	   *representation = palloc(11);
+	char	   *str = palloc(11);
 
-	snprintf(representation, 11, "%lu", (unsigned long) transactionId);
+	snprintf(str, 11, "%lu", (unsigned long) transactionId);
 
-	PG_RETURN_CSTRING(representation);
+	PG_RETURN_CSTRING(str);
 }
 
-/* ----------------------------------------------------------------
- *		xideq
- * ----------------------------------------------------------------
- */
-
 /*
- *		xideq			- returns 1, iff xid1 == xid2
- *								  0  else;
+ *		xideq			- are two xids equal?
  */
 Datum
 xideq(PG_FUNCTION_ARGS)
@@ -64,3 +53,19 @@ xideq(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(TransactionIdEquals(xid1, xid2));
 }
+
+/*
+ *		xid_age			- compute age of an XID (relative to current xact)
+ */
+Datum
+xid_age(PG_FUNCTION_ARGS)
+{
+	TransactionId xid = PG_GETARG_TRANSACTIONID(0);
+	TransactionId now = GetCurrentTransactionId();
+
+	/* Permanent XIDs are always infinitely old */
+	if (! TransactionIdIsNormal(xid))
+		PG_RETURN_INT32(INT_MAX);
+
+	PG_RETURN_INT32((int32) (now - xid));
+}
diff --git a/src/backend/catalog/genbki.sh b/src/backend/catalog/genbki.sh
index 3d51423af426b0e920bf42e8ec4dc87d9928d710..25c8d7d2d4d3873c27be82feeeae4f731720c2d6 100644
--- a/src/backend/catalog/genbki.sh
+++ b/src/backend/catalog/genbki.sh
@@ -10,7 +10,7 @@
 #
 #
 # IDENTIFICATION
-#    $Header: /cvsroot/pgsql/src/backend/catalog/Attic/genbki.sh,v 1.22 2001/08/24 14:07:48 petere Exp $
+#    $Header: /cvsroot/pgsql/src/backend/catalog/Attic/genbki.sh,v 1.23 2001/08/26 16:55:59 tgl Exp $
 #
 # NOTES
 #    non-essential whitespace is removed from the generated file.
@@ -155,12 +155,14 @@ INDEXMAXKEYS4=`expr $INDEXMAXKEYS '*' 4` || exit
 touch ${OUTPUT_PREFIX}.description.$$
 
 # ----------------
-# 	strip comments and trash from .h before we generate
-#	the .bki file...
+# 	Strip comments and other trash from .h
+#
+#	Put multi-line start/end comments on a separate line
+#
+#	Rename datatypes that have different names in .h files than in SQL
+#
+#	Substitute values of configuration constants
 # ----------------
-#	also, change Oid to oid. -- AY 8/94.
-#	also, change NameData to name. -- jolly 8/21/95.
-#	put multi-line start/end comments on a separate line
 #
 cat $INFILES | \
 sed -e 's;/\*.*\*/;;g' \
@@ -173,11 +175,14 @@ sed -e 's;/\*.*\*/;;g' \
 sed -e "s/;[ 	]*$//g" \
     -e "s/^[ 	]*//" \
     -e "s/[ 	]Oid/ oid/g" \
-    -e "s/[ 	]NameData/ name/g" \
     -e "s/^Oid/oid/g" \
+    -e "s/(Oid/(oid/g" \
+    -e "s/[ 	]NameData/ name/g" \
     -e "s/^NameData/name/g" \
     -e "s/(NameData/(name/g" \
-    -e "s/(Oid/(oid/g" \
+    -e "s/[ 	]TransactionId/ xid/g" \
+    -e "s/^TransactionId/xid/g" \
+    -e "s/(TransactionId/(xid/g" \
     -e "s/NAMEDATALEN/$NAMEDATALEN/g" \
     -e "s/DEFAULT_ATTSTATTARGET/$DEFAULTATTSTATTARGET/g" \
     -e "s/INDEX_MAX_KEYS\*2/$INDEXMAXKEYS2/g" \
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index f11b3d9f31921909ccfcf59416e8359bbb6bda20..9debf84cab4385692936b221f7e68342eba3856a 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.162 2001/08/22 18:24:26 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.163 2001/08/26 16:55:59 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -1692,7 +1692,7 @@ IndexBuildHeapScan(Relation heapRelation,
 	TupleTableSlot *slot;
 	ExprContext *econtext;
 	Snapshot	snapshot;
-	TransactionId XmaxRecent;
+	TransactionId OldestXmin;
 
 	/*
 	 * sanity checks
@@ -1731,12 +1731,12 @@ IndexBuildHeapScan(Relation heapRelation,
 	if (IsBootstrapProcessingMode())
 	{
 		snapshot = SnapshotNow;
-		XmaxRecent = InvalidTransactionId;
+		OldestXmin = InvalidTransactionId;
 	}
 	else
 	{
 		snapshot = SnapshotAny;
-		GetXmaxRecent(&XmaxRecent);
+		OldestXmin = GetOldestXmin(heapRelation->rd_rel->relisshared);
 	}
 
 	scan = heap_beginscan(heapRelation, /* relation */
@@ -1769,7 +1769,7 @@ IndexBuildHeapScan(Relation heapRelation,
 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 			sv_infomask = heapTuple->t_data->t_infomask;
 
-			switch (HeapTupleSatisfiesVacuum(heapTuple->t_data, XmaxRecent))
+			switch (HeapTupleSatisfiesVacuum(heapTuple->t_data, OldestXmin))
 			{
 				case HEAPTUPLE_DEAD:
 					indexIt = false;
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index b20525e77d949ea63f0ad23549c7d400f5b53c34..f7f765d9d7a0b70284d13eb6ad25cf664fc18e7b 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v 1.78 2001/08/10 18:57:34 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v 1.79 2001/08/26 16:55:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,8 +39,9 @@
 
 /* non-export function prototypes */
 static bool get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
-			int *encodingP, bool *dbIsTemplateP,
-			Oid *dbLastSysOidP, char *dbpath);
+			int *encodingP, bool *dbIsTemplateP, Oid *dbLastSysOidP,
+			TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
+			char *dbpath);
 static bool get_user_info(Oid use_sysid, bool *use_super, bool *use_createdb);
 static char *resolve_alt_dbpath(const char *dbpath, Oid dboid);
 static bool remove_dbdirs(const char *real_loc, const char *altloc);
@@ -65,6 +66,8 @@ createdb(const char *dbname, const char *dbpath,
 	int			src_encoding;
 	bool		src_istemplate;
 	Oid			src_lastsysoid;
+	TransactionId src_vacuumxid;
+	TransactionId src_frozenxid;
 	char		src_dbpath[MAXPGPATH];
 	Relation	pg_database_rel;
 	HeapTuple	tuple;
@@ -91,7 +94,7 @@ createdb(const char *dbname, const char *dbpath,
 	 * idea, so accept possibility of race to create.  We will check again
 	 * after we grab the exclusive lock.
 	 */
-	if (get_db_info(dbname, NULL, NULL, NULL, NULL, NULL, NULL))
+	if (get_db_info(dbname, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
 		elog(ERROR, "CREATE DATABASE: database \"%s\" already exists", dbname);
 
 	/*
@@ -101,7 +104,9 @@ createdb(const char *dbname, const char *dbpath,
 		dbtemplate = "template1";		/* Default template database name */
 
 	if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
-					 &src_istemplate, &src_lastsysoid, src_dbpath))
+					 &src_istemplate, &src_lastsysoid,
+					 &src_vacuumxid, &src_frozenxid,
+					 src_dbpath))
 		elog(ERROR, "CREATE DATABASE: template \"%s\" does not exist",
 			 dbtemplate);
 
@@ -208,8 +213,10 @@ createdb(const char *dbname, const char *dbpath,
 	pg_database_rel = heap_openr(DatabaseRelationName, AccessExclusiveLock);
 
 	/* Check to see if someone else created same DB name meanwhile. */
-	if (get_db_info(dbname, NULL, NULL, NULL, NULL, NULL, NULL))
+	if (get_db_info(dbname, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
 	{
+		/* Don't hold lock while doing recursive remove */
+		heap_close(pg_database_rel, AccessExclusiveLock);
 		remove_dbdirs(nominal_loc, alt_loc);
 		elog(ERROR, "CREATE DATABASE: database \"%s\" already exists", dbname);
 	}
@@ -227,6 +234,8 @@ createdb(const char *dbname, const char *dbpath,
 	new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(false);
 	new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(true);
 	new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid);
+	new_record[Anum_pg_database_datvacuumxid - 1] = TransactionIdGetDatum(src_vacuumxid);
+	new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
 	/* no nulls here, GetRawDatabaseInfo doesn't like them */
 	new_record[Anum_pg_database_datpath - 1] =
 		DirectFunctionCall1(textin, CStringGetDatum(dbpath ? dbpath : ""));
@@ -307,7 +316,7 @@ dropdb(const char *dbname)
 	pgdbrel = heap_openr(DatabaseRelationName, AccessExclusiveLock);
 
 	if (!get_db_info(dbname, &db_id, &db_owner, NULL,
-					 &db_istemplate, NULL, dbpath))
+					 &db_istemplate, NULL, NULL, NULL, dbpath))
 		elog(ERROR, "DROP DATABASE: database \"%s\" does not exist", dbname);
 
 	if (!use_super && GetUserId() != db_owner)
@@ -397,13 +406,15 @@ dropdb(const char *dbname)
 
 static bool
 get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
-			int *encodingP, bool *dbIsTemplateP,
-			Oid *dbLastSysOidP, char *dbpath)
+			int *encodingP, bool *dbIsTemplateP, Oid *dbLastSysOidP,
+			TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
+			char *dbpath)
 {
 	Relation	relation;
 	ScanKeyData scanKey;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
+	bool		gottuple;
 
 	AssertArg(name);
 
@@ -414,12 +425,11 @@ get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
 						   F_NAMEEQ, NameGetDatum(name));
 
 	scan = heap_beginscan(relation, 0, SnapshotNow, 1, &scanKey);
-	if (!HeapScanIsValid(scan))
-		elog(ERROR, "Cannot begin scan of %s", DatabaseRelationName);
 
 	tuple = heap_getnext(scan, 0);
 
-	if (HeapTupleIsValid(tuple))
+	gottuple = HeapTupleIsValid(tuple);
+	if (gottuple)
 	{
 		Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
 		text	   *tmptext;
@@ -428,7 +438,7 @@ get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
 		/* oid of the database */
 		if (dbIdP)
 			*dbIdP = tuple->t_data->t_oid;
-		/* uid of the owner */
+		/* sysid of the owner */
 		if (ownerIdP)
 			*ownerIdP = dbform->datdba;
 		/* multibyte encoding */
@@ -440,6 +450,12 @@ get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
 		/* last system OID used in database */
 		if (dbLastSysOidP)
 			*dbLastSysOidP = dbform->datlastsysoid;
+		/* limit of vacuumed XIDs */
+		if (dbVacuumXidP)
+			*dbVacuumXidP = dbform->datvacuumxid;
+		/* limit of frozen XIDs */
+		if (dbFrozenXidP)
+			*dbFrozenXidP = dbform->datfrozenxid;
 		/* database path (as registered in pg_database) */
 		if (dbpath)
 		{
@@ -462,7 +478,7 @@ get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
 	heap_endscan(scan);
 	heap_close(relation, AccessShareLock);
 
-	return HeapTupleIsValid(tuple);
+	return gottuple;
 }
 
 static bool
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 9a9d90fa2cf1e5b8bd95e0dab2dcc017c19b5bb7..fe72ff96020f3d0e1c5c94e32d0007d3dab6aa34 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.207 2001/08/10 18:57:35 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.208 2001/08/26 16:55:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,11 +21,13 @@
 
 #include <unistd.h>
 
+#include "access/clog.h"
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/catname.h"
+#include "catalog/pg_database.h"
 #include "catalog/pg_index.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -108,15 +110,24 @@ static MemoryContext vac_context = NULL;
 
 static int	MESSAGE_LEVEL;		/* message level */
 
-static TransactionId XmaxRecent;
+static TransactionId OldestXmin;
+static TransactionId FreezeLimit;
+
+static TransactionId initialOldestXmin;
+static TransactionId initialFreezeLimit;
 
 
 /* non-export function prototypes */
-static void vacuum_init(void);
-static void vacuum_shutdown(void);
+static void vacuum_init(VacuumStmt *vacstmt);
+static void vacuum_shutdown(VacuumStmt *vacstmt);
 static VRelList getrels(Name VacRelP, const char *stmttype);
+static void vac_update_dbstats(Oid dbid,
+							   TransactionId vacuumXID,
+							   TransactionId frozenXID);
+static void vac_truncate_clog(TransactionId vacuumXID,
+							  TransactionId frozenXID);
 static void vacuum_rel(Oid relid, VacuumStmt *vacstmt);
-static void full_vacuum_rel(Relation onerel);
+static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
 static void scan_heap(VRelStats *vacrelstats, Relation onerel,
 					  VacPageList vacuum_pages, VacPageList fraged_pages);
 static void repair_frag(VRelStats *vacrelstats, Relation onerel,
@@ -213,7 +224,7 @@ vacuum(VacuumStmt *vacstmt)
 	/*
 	 * Start up the vacuum cleaner.
 	 */
-	vacuum_init();
+	vacuum_init(vacstmt);
 
 	/*
 	 * Process each selected relation.  We are careful to process
@@ -230,21 +241,8 @@ vacuum(VacuumStmt *vacstmt)
 			analyze_rel(cur->vrl_relid, vacstmt);
 	}
 
-	/*
-	 * If we did a complete vacuum, then flush the init file that relcache.c
-	 * uses to save startup time. The next backend startup will rebuild the
-	 * init file with up-to-date information from pg_class.  This lets the
-	 * optimizer see the stats that we've collected for certain critical
-	 * system indexes.  See relcache.c for more details.
-	 *
-	 * Ignore any failure to unlink the file, since it might not be there if
-	 * no backend has been started since the last vacuum.
-	 */
-	if (vacstmt->vacrel == NULL)
-		unlink(RELCACHE_INIT_FILENAME);
-
 	/* clean up */
-	vacuum_shutdown();
+	vacuum_shutdown(vacstmt);
 }
 
 /*
@@ -268,20 +266,68 @@ vacuum(VacuumStmt *vacstmt)
  *		PostgresMain().
  */
 static void
-vacuum_init(void)
+vacuum_init(VacuumStmt *vacstmt)
 {
+	if (vacstmt->vacuum && vacstmt->vacrel == NULL)
+	{
+		/*
+		 * Compute the initially applicable OldestXmin and FreezeLimit XIDs,
+		 * so that we can record these values at the end of the VACUUM.
+		 * Note that individual tables may well be processed with newer values,
+		 * but we can guarantee that no (non-shared) relations are processed
+		 * with older ones.
+		 *
+		 * It is okay to record non-shared values in pg_database, even though
+		 * we may vacuum shared relations with older cutoffs, because only
+		 * the minimum of the values present in pg_database matters.  We
+		 * can be sure that shared relations have at some time been vacuumed
+		 * with cutoffs no worse than the global minimum; for, if there is
+		 * a backend in some other DB with xmin = OLDXMIN that's determining
+		 * the cutoff with which we vacuum shared relations, it is not possible
+		 * for that database to have a cutoff newer than OLDXMIN recorded in
+		 * pg_database.
+		 */
+		vacuum_set_xid_limits(vacstmt, false,
+							  &initialOldestXmin, &initialFreezeLimit);
+	}
+
 	/* matches the StartTransaction in PostgresMain() */
 	CommitTransactionCommand();
 }
 
 static void
-vacuum_shutdown(void)
+vacuum_shutdown(VacuumStmt *vacstmt)
 {
 	/* on entry, we are not in a transaction */
 
 	/* matches the CommitTransaction in PostgresMain() */
 	StartTransactionCommand();
 
+	/*
+	 * If we did a database-wide VACUUM, update the database's pg_database
+	 * row with info about the transaction IDs used, and try to truncate
+	 * pg_clog.
+	 */
+	if (vacstmt->vacuum && vacstmt->vacrel == NULL)
+	{
+		vac_update_dbstats(MyDatabaseId,
+						   initialOldestXmin, initialFreezeLimit);
+		vac_truncate_clog(initialOldestXmin, initialFreezeLimit);
+	}
+
+	/*
+	 * If we did a complete vacuum or analyze, then flush the init file that
+	 * relcache.c uses to save startup time. The next backend startup will
+	 * rebuild the init file with up-to-date information from pg_class.
+	 * This lets the optimizer see the stats that we've collected for certain
+	 * critical system indexes.  See relcache.c for more details.
+	 *
+	 * Ignore any failure to unlink the file, since it might not be there if
+	 * no backend has been started since the last vacuum.
+	 */
+	if (vacstmt->vacrel == NULL)
+		unlink(RELCACHE_INIT_FILENAME);
+
 	/*
 	 * Clean up working storage --- note we must do this after
 	 * StartTransactionCommand, else we might be trying to delete the
@@ -382,6 +428,52 @@ getrels(Name VacRelP, const char *stmttype)
 	return vrl;
 }
 
+/*
+ * vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
+ */
+void
+vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel,
+					  TransactionId *oldestXmin,
+					  TransactionId *freezeLimit)
+{
+	TransactionId limit;
+
+	*oldestXmin = GetOldestXmin(sharedRel);
+
+	Assert(TransactionIdIsNormal(*oldestXmin));
+
+	if (vacstmt->freeze)
+	{
+		/* FREEZE option: use oldest Xmin as freeze cutoff too */
+		limit = *oldestXmin;
+	}
+	else
+	{
+		/*
+		 * Normal case: freeze cutoff is well in the past, to wit, about
+		 * halfway to the wrap horizon
+		 */
+		limit = GetCurrentTransactionId() - (MaxTransactionId >> 2);
+	}
+
+	/*
+	 * Be careful not to generate a "permanent" XID
+	 */
+	if (!TransactionIdIsNormal(limit))
+		limit = FirstNormalTransactionId;
+
+	/*
+	 * Ensure sane relationship of limits
+	 */
+	if (TransactionIdFollows(limit, *oldestXmin))
+	{
+		elog(NOTICE, "oldest Xmin is far in the past --- close open transactions soon to avoid wraparound problems");
+		limit = *oldestXmin;
+	}
+
+	*freezeLimit = limit;
+}
+
 
 /*
  *	vac_update_relstats() -- update statistics for one relation
@@ -449,6 +541,122 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples,
 }
 
 
+/*
+ *	vac_update_dbstats() -- update statistics for one database
+ *
+ *		Update the whole-database statistics that are kept in its pg_database
+ *		row.
+ *
+ *		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; which is good since GetRawDatabaseInfo
+ *		can get confused by finding dead tuples in pg_database.
+ *
+ *		This routine is shared by full and lazy VACUUM.  Note that it is only
+ *		applied after a database-wide VACUUM operation.
+ */
+static void
+vac_update_dbstats(Oid dbid,
+				   TransactionId vacuumXID,
+				   TransactionId frozenXID)
+{
+	Relation	relation;
+	ScanKeyData entry[1];
+	HeapScanDesc scan;
+	HeapTuple	tuple;
+	Form_pg_database dbform;
+
+	relation = heap_openr(DatabaseRelationName, RowExclusiveLock);
+
+	/* Must use a heap scan, since there's no syscache for pg_database */
+	ScanKeyEntryInitialize(&entry[0], 0x0,
+						   ObjectIdAttributeNumber, F_OIDEQ,
+						   ObjectIdGetDatum(dbid));
+
+	scan = heap_beginscan(relation, 0, SnapshotNow, 1, entry);
+
+	tuple = heap_getnext(scan, 0);
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "database %u does not exist", dbid);
+
+	dbform = (Form_pg_database) GETSTRUCT(tuple);
+
+	/* overwrite the existing statistics in the tuple */
+	dbform->datvacuumxid = vacuumXID;
+	dbform->datfrozenxid = frozenXID;
+
+	/* invalidate the tuple in the cache and write the buffer */
+	RelationInvalidateHeapTuple(relation, tuple);
+	WriteNoReleaseBuffer(scan->rs_cbuf);
+
+	heap_endscan(scan);
+
+	heap_close(relation, RowExclusiveLock);
+}
+
+
+/*
+ *	vac_truncate_clog() -- attempt to truncate the commit log
+ *
+ *		Scan pg_database to determine the system-wide oldest datvacuumxid,
+ *		and use it to truncate the transaction commit log (pg_clog).
+ *		Also generate a warning if the system-wide oldest datfrozenxid
+ *		seems to be in danger of wrapping around.
+ *
+ *		The passed XIDs are simply the ones I just wrote into my pg_database
+ *		entry.  They're used to initialize the "min" calculations.
+ *
+ *		This routine is shared by full and lazy VACUUM.  Note that it is only
+ *		applied after a database-wide VACUUM operation.
+ */
+static void
+vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID)
+{
+	Relation	relation;
+	HeapScanDesc scan;
+	HeapTuple	tuple;
+	int32		age;
+
+	relation = heap_openr(DatabaseRelationName, AccessShareLock);
+
+	scan = heap_beginscan(relation, 0, SnapshotNow, 0, NULL);
+
+	while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+	{
+		Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
+
+		/* Ignore non-connectable databases (eg, template0) */
+		/* It's assumed that these have been frozen correctly */
+		if (!dbform->datallowconn)
+			continue;
+
+		if (TransactionIdIsNormal(dbform->datvacuumxid) &&
+			TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID))
+			vacuumXID = dbform->datvacuumxid;
+		if (TransactionIdIsNormal(dbform->datfrozenxid) &&
+			TransactionIdPrecedes(dbform->datfrozenxid, frozenXID))
+			frozenXID = dbform->datfrozenxid;
+	}
+
+	heap_endscan(scan);
+
+	heap_close(relation, AccessShareLock);
+
+	/* Truncate CLOG to the oldest vacuumxid */
+	TruncateCLOG(vacuumXID);
+
+	/* Give warning about impending wraparound problems */
+	age = (int32) (GetCurrentTransactionId() - frozenXID);
+	if (age > (int32) ((MaxTransactionId >> 3) * 3))
+		elog(NOTICE, "Some databases have not been vacuumed in %d transactions."
+			 "\n\tBetter vacuum them within %d transactions,"
+			 "\n\tor you may have a wraparound failure.",
+			 age, (int32) (MaxTransactionId >> 1) - age);
+}
+
+
 /****************************************************************************
  *																			*
  *			Code common to both flavors of VACUUM							*
@@ -550,7 +758,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt)
 	 * Do the actual work --- either FULL or "lazy" vacuum
 	 */
 	if (vacstmt->full)
-		full_vacuum_rel(onerel);
+		full_vacuum_rel(onerel, vacstmt);
 	else
 		lazy_vacuum_rel(onerel, vacstmt);
 
@@ -597,7 +805,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt)
  *		and locked the relation.
  */
 static void
-full_vacuum_rel(Relation onerel)
+full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
 {
 	VacPageListData vacuum_pages;		/* List of pages to vacuum and/or
 										 * clean indexes */
@@ -613,7 +821,8 @@ full_vacuum_rel(Relation onerel)
 		IsSystemRelationName(RelationGetRelationName(onerel)))
 		reindex = true;
 
-	GetXmaxRecent(&XmaxRecent);
+	vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared,
+						  &OldestXmin, &FreezeLimit);
 
 	/*
 	 * Set up statistics-gathering machinery.
@@ -845,12 +1054,25 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
 			tupgone = false;
 			sv_infomask = tuple.t_data->t_infomask;
 
-			switch (HeapTupleSatisfiesVacuum(tuple.t_data, XmaxRecent))
+			switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin))
 			{
 				case HEAPTUPLE_DEAD:
 					tupgone = true;	/* we can delete the tuple */
 					break;
 				case HEAPTUPLE_LIVE:
+					/*
+					 * Tuple is good.  Consider whether to replace its xmin
+					 * value with FrozenTransactionId.
+					 */
+					if (TransactionIdIsNormal(tuple.t_data->t_xmin) &&
+						TransactionIdPrecedes(tuple.t_data->t_xmin,
+											  FreezeLimit))
+					{
+						tuple.t_data->t_xmin = FrozenTransactionId;
+						tuple.t_data->t_infomask &= ~HEAP_XMIN_INVALID;
+						tuple.t_data->t_infomask |= HEAP_XMIN_COMMITTED;
+						pgchanged = true;
+					}
 					break;
 				case HEAPTUPLE_RECENTLY_DEAD:
 					/*
@@ -1312,7 +1534,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 			 * tuples to another places.
 			 */
 			if ((tuple.t_data->t_infomask & HEAP_UPDATED &&
-				 !TransactionIdPrecedes(tuple.t_data->t_xmin, XmaxRecent)) ||
+				 !TransactionIdPrecedes(tuple.t_data->t_xmin, OldestXmin)) ||
 				(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) &&
 				 !(ItemPointerEquals(&(tuple.t_self),
 									 &(tuple.t_data->t_ctid)))))
@@ -1362,7 +1584,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 
 						/*
 						 * This means that in the middle of chain there
-						 * was tuple updated by older (than XmaxRecent)
+						 * was tuple updated by older (than OldestXmin)
 						 * xaction and this tuple is already deleted by
 						 * me. Actually, upper part of chain should be
 						 * removed and seems that this should be handled
@@ -1430,7 +1652,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
 
 					/* All done ? */
 					if (!(tp.t_data->t_infomask & HEAP_UPDATED) ||
-						TransactionIdPrecedes(tp.t_data->t_xmin, XmaxRecent))
+						TransactionIdPrecedes(tp.t_data->t_xmin, OldestXmin))
 						break;
 
 					/* Well, try to find tuple with old row version */
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index bdde6114133b14b4857de5b721699ba2219a6e1f..f525ecaf36c609bfafad2d1b767bd27d0259b517 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -31,7 +31,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.4 2001/08/10 18:57:35 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.5 2001/08/26 16:55:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -94,7 +94,8 @@ typedef struct LVRelStats
 
 static int	MESSAGE_LEVEL;		/* message level */
 
-static TransactionId XmaxRecent;
+static TransactionId OldestXmin;
+static TransactionId FreezeLimit;
 
 
 /* non-export function prototypes */
@@ -143,7 +144,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
 	else
 		MESSAGE_LEVEL = DEBUG;
 
-	GetXmaxRecent(&XmaxRecent);
+	vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared,
+						  &OldestXmin, &FreezeLimit);
 
 	vacrelstats = (LVRelStats *) palloc(sizeof(LVRelStats));
 	MemSet(vacrelstats, 0, sizeof(LVRelStats));
@@ -307,12 +309,25 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			tupgone = false;
 			sv_infomask = tuple.t_data->t_infomask;
 
-			switch (HeapTupleSatisfiesVacuum(tuple.t_data, XmaxRecent))
+			switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin))
 			{
 				case HEAPTUPLE_DEAD:
 					tupgone = true;	/* we can delete the tuple */
 					break;
 				case HEAPTUPLE_LIVE:
+					/*
+					 * Tuple is good.  Consider whether to replace its xmin
+					 * value with FrozenTransactionId.
+					 */
+					if (TransactionIdIsNormal(tuple.t_data->t_xmin) &&
+						TransactionIdPrecedes(tuple.t_data->t_xmin,
+											  FreezeLimit))
+					{
+						tuple.t_data->t_xmin = FrozenTransactionId;
+						tuple.t_data->t_infomask &= ~HEAP_XMIN_INVALID;
+						tuple.t_data->t_infomask |= HEAP_XMIN_COMMITTED;
+						pgchanged = true;
+					}
 					break;
 				case HEAPTUPLE_RECENTLY_DEAD:
 					/*
@@ -783,12 +798,13 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
 			tupgone = false;
 			sv_infomask = tuple.t_data->t_infomask;
 
-			switch (HeapTupleSatisfiesVacuum(tuple.t_data, XmaxRecent))
+			switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin))
 			{
 				case HEAPTUPLE_DEAD:
 					tupgone = true;	/* we can delete the tuple */
 					break;
 				case HEAPTUPLE_LIVE:
+					/* Shouldn't be necessary to re-freeze anything */
 					break;
 				case HEAPTUPLE_RECENTLY_DEAD:
 					/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 26da8d479370eb68696652447a5032a873d691d2..cf9ad2e983c5967e3984ff11b4b277806547e115 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.154 2001/08/21 16:36:02 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.155 2001/08/26 16:55:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2246,6 +2246,7 @@ _copyVacuumStmt(VacuumStmt *from)
 	newnode->vacuum = from->vacuum;
 	newnode->full = from->full;
 	newnode->analyze = from->analyze;
+	newnode->freeze = from->freeze;
 	newnode->verbose = from->verbose;
 	if (from->vacrel)
 		newnode->vacrel = pstrdup(from->vacrel);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c2cd2109fe97f9025a7642a989bfaae24aebdd58..9dce1f61b46111754d4b03cd5b4dc06b10578a08 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.102 2001/08/21 16:36:02 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.103 2001/08/26 16:55:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1116,6 +1116,8 @@ _equalVacuumStmt(VacuumStmt *a, VacuumStmt *b)
 		return false;
 	if (a->analyze != b->analyze)
 		return false;
+	if (a->freeze != b->freeze)
+		return false;
 	if (a->verbose != b->verbose)
 		return false;
 	if (!equalstr(a->vacrel, b->vacrel))
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 84ef63e6295e51517a9a6b23fe26e292be65dfc2..8d5e9abb07d9ff6844ddcc4c383168e7e630ceb5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.248 2001/08/25 18:52:41 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.249 2001/08/26 16:55:59 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -215,7 +215,7 @@ static void doNegateFloat(Value *v);
 
 %type <boolean>	opt_binary, opt_using, opt_instead, opt_cursor
 %type <boolean>	opt_with_copy, index_opt_unique, opt_verbose, opt_full
-%type <boolean>	analyze_keyword
+%type <boolean>	opt_freeze, analyze_keyword
 
 %type <ival>	copy_dirn, direction, reindex_type, drop_type,
 		opt_column, event, comment_type, comment_cl,
@@ -346,7 +346,7 @@ static void doNegateFloat(Value *v);
 		CACHE, CHECKPOINT, CLUSTER, COMMENT, COPY, CREATEDB, CREATEUSER, CYCLE,
 		DATABASE, DELIMITERS, DO,
 		EACH, ENCODING, EXCLUSIVE, EXPLAIN,
-		FORCE, FORWARD, FUNCTION, HANDLER,
+		FORCE, FORWARD, FREEZE, FUNCTION, HANDLER,
 		ILIKE, INCREMENT, INDEX, INHERITS, INSTEAD, ISNULL,
 		LANCOMPILER, LIMIT, LISTEN, LOAD, LOCATION, LOCK_P,
 		MAXVALUE, MINVALUE, MODE, MOVE,
@@ -3082,34 +3082,37 @@ ClusterStmt:  CLUSTER index_name ON relation_name
  *
  *****************************************************************************/
 
-VacuumStmt:  VACUUM opt_full opt_verbose
+VacuumStmt:  VACUUM opt_full opt_freeze opt_verbose
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
 					n->vacuum = true;
 					n->analyze = false;
 					n->full = $2;
-					n->verbose = $3;
+					n->freeze = $3;
+					n->verbose = $4;
 					n->vacrel = NULL;
 					n->va_cols = NIL;
 					$$ = (Node *)n;
 				}
-		| VACUUM opt_full opt_verbose relation_name
+		| VACUUM opt_full opt_freeze opt_verbose relation_name
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
 					n->vacuum = true;
 					n->analyze = false;
 					n->full = $2;
-					n->verbose = $3;
-					n->vacrel = $4;
+					n->freeze = $3;
+					n->verbose = $4;
+					n->vacrel = $5;
 					n->va_cols = NIL;
 					$$ = (Node *)n;
 				}
-		| VACUUM opt_full opt_verbose AnalyzeStmt
+		| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
 				{
-					VacuumStmt *n = (VacuumStmt *) $4;
+					VacuumStmt *n = (VacuumStmt *) $5;
 					n->vacuum = true;
 					n->full = $2;
-					n->verbose |= $3;
+					n->freeze = $3;
+					n->verbose |= $4;
 					$$ = (Node *)n;
 				}
 		;
@@ -3120,6 +3123,7 @@ AnalyzeStmt:  analyze_keyword opt_verbose
 					n->vacuum = false;
 					n->analyze = true;
 					n->full = false;
+					n->freeze = false;
 					n->verbose = $2;
 					n->vacrel = NULL;
 					n->va_cols = NIL;
@@ -3131,6 +3135,7 @@ AnalyzeStmt:  analyze_keyword opt_verbose
 					n->vacuum = false;
 					n->analyze = true;
 					n->full = false;
+					n->freeze = false;
 					n->verbose = $2;
 					n->vacrel = $3;
 					n->va_cols = $4;
@@ -3150,6 +3155,10 @@ opt_full:  FULL									{ $$ = TRUE; }
 		| /*EMPTY*/								{ $$ = FALSE; }
 		;
 
+opt_freeze:  FREEZE								{ $$ = TRUE; }
+		| /*EMPTY*/								{ $$ = FALSE; }
+		;
+
 opt_name_list:  '(' name_list ')'				{ $$ = $2; }
 		| /*EMPTY*/								{ $$ = NIL; }
 		;
@@ -5615,6 +5624,7 @@ TokenId:  ABSOLUTE						{ $$ = "absolute"; }
 		| DROP							{ $$ = "drop"; }
 		| EACH							{ $$ = "each"; }
 		| ENCODING						{ $$ = "encoding"; }
+		| ENCRYPTED						{ $$ = "encrypted"; }
 		| ESCAPE						{ $$ = "escape"; }
 		| EXCLUSIVE						{ $$ = "exclusive"; }
 		| EXECUTE						{ $$ = "execute"; }
@@ -5693,6 +5703,7 @@ TokenId:  ABSOLUTE						{ $$ = "absolute"; }
 		| TRUNCATE						{ $$ = "truncate"; }
 		| TRUSTED						{ $$ = "trusted"; }
 		| TYPE_P						{ $$ = "type"; }
+		| UNENCRYPTED					{ $$ = "unencrypted"; }
 		| UNLISTEN						{ $$ = "unlisten"; }
 		| UNTIL							{ $$ = "until"; }
 		| UPDATE						{ $$ = "update"; }
@@ -5753,7 +5764,6 @@ ColLabel:  ColId						{ $$ = $1; }
 		| DISTINCT						{ $$ = "distinct"; }
 		| DO							{ $$ = "do"; }
 		| ELSE							{ $$ = "else"; }
-		| ENCRYPTED						{ $$ = "encrypted"; }
 		| END_TRANS						{ $$ = "end"; }
 		| EXCEPT						{ $$ = "except"; }
 		| EXISTS						{ $$ = "exists"; }
@@ -5763,6 +5773,7 @@ ColLabel:  ColId						{ $$ = $1; }
 		| FLOAT							{ $$ = "float"; }
 		| FOR							{ $$ = "for"; }
 		| FOREIGN						{ $$ = "foreign"; }
+		| FREEZE						{ $$ = "freeze"; }
 		| FROM							{ $$ = "from"; }
 		| FULL							{ $$ = "full"; }
 		| GLOBAL						{ $$ = "global"; }
@@ -5825,7 +5836,6 @@ ColLabel:  ColId						{ $$ = $1; }
 		| TRANSACTION					{ $$ = "transaction"; }
 		| TRIM							{ $$ = "trim"; }
 		| TRUE_P						{ $$ = "true"; }
-		| UNENCRYPTED					{ $$ = "unencrypted"; }
 		| UNION							{ $$ = "union"; }
 		| UNIQUE						{ $$ = "unique"; }
 		| UNKNOWN						{ $$ = "unknown"; }
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 5c1427da03ec5c645c4c715d6146ab6f78d3b7d3..bff257a545522c64c4507a2c0179b8120a745f95 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.96 2001/08/16 20:38:54 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.97 2001/08/26 16:56:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -118,6 +118,7 @@ static ScanKeyword ScanKeywords[] = {
 	{"force", FORCE},
 	{"foreign", FOREIGN},
 	{"forward", FORWARD},
+	{"freeze", FREEZE},
 	{"from", FROM},
 	{"full", FULL},
 	{"function", FUNCTION},
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 985577c4a6c571c32985bf6f3b51432328fa67b5..1d43b1ead4476274032b5b2b64e8ab411ef2acd5 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.39 2001/08/25 18:52:42 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.40 2001/08/26 16:56:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,6 +20,8 @@
 #include "storage/sinval.h"
 #include "storage/sinvaladt.h"
 #include "utils/tqual.h"
+#include "miscadmin.h"
+
 
 SPINLOCK	SInvalLock = (SPINLOCK) NULL;
 
@@ -210,17 +212,23 @@ TransactionIdIsInProgress(TransactionId xid)
 }
 
 /*
- * GetXmaxRecent -- returns oldest transaction that was running
- *					when all current transaction were started.
- *					It's used by vacuum to decide what deleted
- *					tuples must be preserved in a table.
+ * GetOldestXmin -- returns oldest transaction that was running
+ *					when any current transaction was started.
+ *
+ * If allDbs is TRUE then all backends are considered; if allDbs is FALSE
+ * then only backends running in my own database are considered.
  *
- * Note: we include all currently running xids in the set of considered xids.
+ * This is used by VACUUM to decide which deleted tuples must be preserved
+ * in a table.  allDbs = TRUE is needed for shared relations, but allDbs =
+ * FALSE is sufficient for non-shared relations, since only backends in my
+ * own database could ever see the tuples in them.
+ *
+ * Note: we include the currently running xids in the set of considered xids.
  * This ensures that if a just-started xact has not yet set its snapshot,
  * when it does set the snapshot it cannot set xmin less than what we compute.
  */
-void
-GetXmaxRecent(TransactionId *XmaxRecent)
+TransactionId
+GetOldestXmin(bool allDbs)
 {
 	SISeg	   *segP = shmInvalBuffer;
 	ProcState  *stateP = segP->procState;
@@ -238,24 +246,28 @@ GetXmaxRecent(TransactionId *XmaxRecent)
 		if (pOffset != INVALID_OFFSET)
 		{
 			PROC	   *proc = (PROC *) MAKE_PTR(pOffset);
-			/* Fetch xid just once - see GetNewTransactionId */
-			TransactionId xid = proc->xid;
 
-			if (TransactionIdIsNormal(xid))
+			if (allDbs || proc->databaseId == MyDatabaseId)
 			{
-				if (TransactionIdPrecedes(xid, result))
-					result = xid;
-				xid = proc->xmin;
+				/* Fetch xid just once - see GetNewTransactionId */
+				TransactionId xid = proc->xid;
+
 				if (TransactionIdIsNormal(xid))
+				{
 					if (TransactionIdPrecedes(xid, result))
 						result = xid;
+					xid = proc->xmin;
+					if (TransactionIdIsNormal(xid))
+						if (TransactionIdPrecedes(xid, result))
+							result = xid;
+				}
 			}
 		}
 	}
 
 	SpinRelease(SInvalLock);
 
-	*XmaxRecent = result;
+	return result;
 }
 
 /*----------
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index 5f5699c4458e9e5179c72c9805f76bbe72ff9970..f0536d761186ef318f3e8d4c836ad32e81fe6ce1 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/time/tqual.c,v 1.41 2001/08/25 18:52:42 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/time/tqual.c,v 1.42 2001/08/26 16:56:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -592,8 +592,8 @@ HeapTupleSatisfiesSnapshot(HeapTupleHeader tuple, Snapshot snapshot)
  * HeapTupleSatisfiesVacuum - determine tuple status for VACUUM and related
  *		operations
  *
- * XmaxRecent is a cutoff XID (obtained from GetXmaxRecent()).  Tuples
- * deleted by XIDs >= XmaxRecent are deemed "recently dead"; they might
+ * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
+ * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
  * still be visible to some open transaction, so we can't remove them,
  * even if we see that the deleting transaction has committed.
  *
@@ -603,7 +603,7 @@ HeapTupleSatisfiesSnapshot(HeapTupleHeader tuple, Snapshot snapshot)
  * change in t_infomask and scheduling a disk write if so.
  */
 HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId XmaxRecent)
+HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin)
 {
 	/*
 	 * Has inserting transaction committed?
@@ -712,7 +712,7 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId XmaxRecent)
 		return HEAPTUPLE_DEAD;
 	}
 
-	if (!TransactionIdPrecedes(tuple->t_xmax, XmaxRecent))
+	if (!TransactionIdPrecedes(tuple->t_xmax, OldestXmin))
 	{
 		/* deleting xact is too recent, tuple could still be visible */
 		return HEAPTUPLE_RECENTLY_DEAD;
diff --git a/src/bin/initdb/initdb.sh b/src/bin/initdb/initdb.sh
index bbb014391a489498d19a6998737aadc79c7d409b..11799e7049074fc440d307e555816a23ed21f217 100644
--- a/src/bin/initdb/initdb.sh
+++ b/src/bin/initdb/initdb.sh
@@ -27,7 +27,7 @@
 # Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
 # Portions Copyright (c) 1994, Regents of the University of California
 #
-# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.134 2001/08/25 18:52:42 tgl Exp $
+# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.135 2001/08/26 16:56:00 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -841,7 +841,7 @@ echo "ok"
 $ECHO_N "vacuuming database template1... "$ECHO_C
 
 "$PGPATH"/postgres $PGSQL_OPT template1 >/dev/null <<EOF
-VACUUM FULL ANALYZE;
+VACUUM FULL FREEZE;
 EOF
 if [ "$?" -ne 0 ]; then
     exit_nicely
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index f0d213361ac73a8689c6fd7149262e02a14ab1a8..5ce62d289f980790596749374aca69ab133a63c4 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: transam.h,v 1.39 2001/08/25 18:52:42 tgl Exp $
+ * $Id: transam.h,v 1.40 2001/08/26 16:56:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,6 +31,7 @@
 #define BootstrapTransactionId		((TransactionId) 1)
 #define FrozenTransactionId			((TransactionId) 2)
 #define FirstNormalTransactionId	((TransactionId) 3)
+#define MaxTransactionId			((TransactionId) 0xFFFFFFFF)
 
 /* ----------------
  *		transaction ID manipulation macros
@@ -38,11 +39,7 @@
  */
 #define TransactionIdIsValid(xid)		((xid) != InvalidTransactionId)
 #define TransactionIdIsNormal(xid)		((xid) >= FirstNormalTransactionId)
-#define TransactionIdEquals(id1, id2)			((id1) == (id2))
-#define TransactionIdPrecedes(id1, id2)			((id1) < (id2))
-#define TransactionIdPrecedesOrEquals(id1, id2)	((id1) <= (id2))
-#define TransactionIdFollows(id1, id2)			((id1) > (id2))
-#define TransactionIdFollowsOrEquals(id1, id2)	((id1) >= (id2))
+#define TransactionIdEquals(id1, id2)	((id1) == (id2))
 #define TransactionIdStore(xid, dest)	(*(dest) = (xid))
 #define StoreInvalidTransactionId(dest)	(*(dest) = InvalidTransactionId)
 /* advance a transaction ID variable, handling wraparound correctly */
@@ -105,6 +102,10 @@ extern bool TransactionIdDidCommit(TransactionId transactionId);
 extern bool TransactionIdDidAbort(TransactionId transactionId);
 extern void TransactionIdCommit(TransactionId transactionId);
 extern void TransactionIdAbort(TransactionId transactionId);
+extern bool TransactionIdPrecedes(TransactionId id1, TransactionId id2);
+extern bool TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2);
+extern bool TransactionIdFollows(TransactionId id1, TransactionId id2);
+extern bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2);
 
 /* in transam/varsup.c */
 extern TransactionId GetNewTransactionId(void);
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index a9c7b674a95a22e48882bceb2995912b15ac1a11..55c043511778e8edaa455808baa430c2fbb05f28 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: xact.h,v 1.35 2001/08/25 18:52:42 tgl Exp $
+ * $Id: xact.h,v 1.36 2001/08/26 16:56:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -130,5 +130,6 @@ extern void xact_desc(char *buf, uint8 xl_info, char *rec);
 extern Datum xidin(PG_FUNCTION_ARGS);
 extern Datum xidout(PG_FUNCTION_ARGS);
 extern Datum xideq(PG_FUNCTION_ARGS);
+extern Datum xid_age(PG_FUNCTION_ARGS);
 
 #endif	 /* XACT_H */
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 58d43a652931c6fd76dfacb96299123152ceb38a..0b67a37ad22aedd311ee827cd62f35f67f5b0a28 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.92 2001/08/25 18:52:42 tgl Exp $
+ * $Id: catversion.h,v 1.93 2001/08/26 16:56:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200108241
+#define CATALOG_VERSION_NO	200108251
 
 #endif
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index eb1ba77235e87365d54ab4d173e17151ebed7add..826d2b8ea849517029f29797398d269ec21572de 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_attribute.h,v 1.75 2001/08/25 18:52:42 tgl Exp $
+ * $Id: pg_attribute.h,v 1.76 2001/08/26 16:56:00 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -276,8 +276,10 @@ DATA(insert ( 1262 encoding			23 0  4   3 0 -1 -1 t p f i f f));
 DATA(insert ( 1262 datistemplate	16 0  1   4 0 -1 -1 t p f c f f));
 DATA(insert ( 1262 datallowconn		16 0  1   5 0 -1 -1 t p f c f f));
 DATA(insert ( 1262 datlastsysoid	26 0  4   6 0 -1 -1 t p f i f f));
+DATA(insert ( 1262 datvacuumxid		28 0  4   7 0 -1 -1 t p f i f f));
+DATA(insert ( 1262 datfrozenxid		28 0  4   8 0 -1 -1 t p f i f f));
 /* do not mark datpath as toastable; GetRawDatabaseInfo won't cope */
-DATA(insert ( 1262 datpath			25 0 -1   7 0 -1 -1 f p f i f f));
+DATA(insert ( 1262 datpath			25 0 -1   9 0 -1 -1 f p f i f f));
 DATA(insert ( 1262 ctid				27 0  6  -1 0 -1 -1 f p f i f f));
 DATA(insert ( 1262 oid				26 0  4  -2 0 -1 -1 t p f i f f));
 DATA(insert ( 1262 xmin				28 0  4  -3 0 -1 -1 t p f i f f));
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 4f2708e3bd08f045126bc72beb4a609de5176e16..2d0103a87b8397aa8ca117887018e548c3d94c1c 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_class.h,v 1.53 2001/08/25 18:52:43 tgl Exp $
+ * $Id: pg_class.h,v 1.54 2001/08/26 16:56:01 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -142,7 +142,7 @@ DATA(insert OID = 1260 (  pg_shadow		86	PGUID 0 1260 0 0 0 0 f t r 8  0 0 0 0 0
 DESCR("");
 DATA(insert OID = 1261 (  pg_group		87	PGUID 0 1261 0 0 0 0 f t r 3  0 0 0 0 0 f f f f _null_ ));
 DESCR("");
-DATA(insert OID = 1262 (  pg_database	88	PGUID 0 1262 0 0 0 0 f t r 7  0 0 0 0 0 t f f f _null_ ));
+DATA(insert OID = 1262 (  pg_database	88	PGUID 0 1262 0 0 0 0 f t r 9  0 0 0 0 0 t f f f _null_ ));
 DESCR("");
 DATA(insert OID = 376  (  pg_xactlock	0	PGUID 0    0 0 0 0 0 f t s 1  0 0 0 0 0 f f f f _null_ ));
 DESCR("");
diff --git a/src/include/catalog/pg_database.h b/src/include/catalog/pg_database.h
index ffc83da3b4cfc7ccd08dbcc9c896660d0b1ca82b..f3e0fbd99b7ba0072d3ac393e180849d5117e42a 100644
--- a/src/include/catalog/pg_database.h
+++ b/src/include/catalog/pg_database.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_database.h,v 1.17 2001/03/22 04:00:38 momjian Exp $
+ * $Id: pg_database.h,v 1.18 2001/08/26 16:56:02 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -33,13 +33,14 @@
  */
 CATALOG(pg_database) BOOTSTRAP
 {
-	NameData	datname;
-	int4		datdba;
-	int4		encoding;
-	bool		datistemplate;	/* allowed as template for CREATE
-								 * DATABASE? */
+	NameData	datname;		/* database name */
+	int4		datdba;			/* sysid of owner */
+	int4		encoding;		/* multibyte encoding, if any */
+	bool		datistemplate;	/* allowed as CREATE DATABASE template? */
 	bool		datallowconn;	/* new connections allowed? */
-	Oid			datlastsysoid;
+	Oid			datlastsysoid;	/* highest OID to consider a system OID */
+	TransactionId datvacuumxid;	/* all XIDs before this are vacuumed */
+	TransactionId datfrozenxid;	/* all XIDs before this are frozen */
 	text		datpath;		/* VARIABLE LENGTH FIELD */
 } FormData_pg_database;
 
@@ -54,16 +55,18 @@ typedef FormData_pg_database *Form_pg_database;
  *		compiler constants for pg_database
  * ----------------
  */
-#define Natts_pg_database				7
+#define Natts_pg_database				9
 #define Anum_pg_database_datname		1
 #define Anum_pg_database_datdba			2
 #define Anum_pg_database_encoding		3
 #define Anum_pg_database_datistemplate	4
 #define Anum_pg_database_datallowconn	5
 #define Anum_pg_database_datlastsysoid	6
-#define Anum_pg_database_datpath		7
+#define Anum_pg_database_datvacuumxid	7
+#define Anum_pg_database_datfrozenxid	8
+#define Anum_pg_database_datpath		9
 
-DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 "" ));
+DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 "" ));
 DESCR("Default template database");
 
 #define TemplateDbOid			1
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2a48dec681fea3550cfa7ed0687ec576a77668d..c8919ff683314111ef9f3bce33c051d868787cc8 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_proc.h,v 1.206 2001/08/16 20:38:54 tgl Exp $
+ * $Id: pg_proc.h,v 1.207 2001/08/26 16:56:02 tgl Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -1450,6 +1450,8 @@ DATA(insert OID = 1179 (  date			   PGUID 12 f t f t 1 f 1082 "702" 100 0 0 100
 DESCR("convert abstime to date");
 DATA(insert OID = 1180 (  abstime		   PGUID 12 f t f t 1 f  702 "1184" 100 0 0 100  timestamp_abstime - ));
 DESCR("convert timestamp to abstime");
+DATA(insert OID = 1181 (  age			   PGUID 12 f t f t 1 f 23 "28" 100 0 0 100  xid_age - ));
+DESCR("age of a transaction ID, in transactions before current transaction");
 
 DATA(insert OID = 1188 (  timestamp_mi		PGUID 12 f t f t 2 f 1186 "1184 1184" 100 0 0 100  timestamp_mi - ));
 DESCR("subtract");
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 0d362bb1801ef58a960f5968b44c9f71bc304877..5d1541ef3ba233f9f9f3467f8786b0a15ba6da46 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: vacuum.h,v 1.39 2001/07/18 00:46:25 tgl Exp $
+ * $Id: vacuum.h,v 1.40 2001/08/26 16:56:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,6 +44,9 @@ extern void vac_update_relstats(Oid relid,
 								BlockNumber num_pages,
 								double num_tuples,
 								bool hasindex);
+extern void vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel,
+								  TransactionId *oldestXmin,
+								  TransactionId *freezeLimit);
 extern bool vac_is_partial_index(Relation indrel);
 extern void vac_init_rusage(VacRUsage *ru0);
 extern const char *vac_show_rusage(VacRUsage *ru0);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a961cb5af4f85781a0eae36e5659acc4435c2897..315ad113ee766c5a1e53a5575f084e1aa2a47f5a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.142 2001/08/21 16:36:06 tgl Exp $
+ * $Id: parsenodes.h,v 1.143 2001/08/26 16:56:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -694,6 +694,7 @@ typedef struct VacuumStmt
 	bool		vacuum;			/* do VACUUM step */
 	bool		full;			/* do FULL (non-concurrent) vacuum */
 	bool		analyze;		/* do ANALYZE step */
+	bool		freeze;			/* early-freeze option */
 	bool		verbose;		/* print progress info */
 	char	   *vacrel;			/* name of single table to process, or NULL */
 	List	   *va_cols;		/* list of column names, or NIL for all */
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index 11aad6095738d8d3f2f151a00d79e4e318eb9a05..2e1ac7bfb155bcfa824e94877c3184cd487fcc5b 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: sinval.h,v 1.20 2001/07/06 21:04:26 tgl Exp $
+ * $Id: sinval.h,v 1.21 2001/08/26 16:56:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -76,7 +76,7 @@ extern void ReceiveSharedInvalidMessages(
 
 extern bool DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself);
 extern bool TransactionIdIsInProgress(TransactionId xid);
-extern void GetXmaxRecent(TransactionId *XmaxRecent);
+extern TransactionId GetOldestXmin(bool allDbs);
 extern int	CountActiveBackends(void);
 /* Use "struct proc", not PROC, to avoid including proc.h here */
 extern struct proc *BackendIdGetProc(BackendId procId);
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index bff437f540bd7d9211b230a5135c90bbac8793e9..cc238e7457bd0a4f13987da69bb62587a8af9b3c 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: tqual.h,v 1.33 2001/08/23 23:06:38 tgl Exp $
+ * $Id: tqual.h,v 1.34 2001/08/26 16:56:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -104,7 +104,7 @@ extern bool HeapTupleSatisfiesSnapshot(HeapTupleHeader tuple,
 						   Snapshot snapshot);
 extern int	HeapTupleSatisfiesUpdate(HeapTuple tuple);
 extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTupleHeader tuple,
-											TransactionId XmaxRecent);
+											TransactionId OldestXmin);
 
 extern Snapshot GetSnapshotData(bool serializable);
 extern void SetQuerySnapshot(void);