Index: contrib/pgbench/pgbench.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/contrib/pgbench/pgbench.c,v
retrieving revision 1.26
diff -c -r1.26 pgbench.c
*** contrib/pgbench/pgbench.c	4 Aug 2003 00:43:11 -0000	1.26
--- contrib/pgbench/pgbench.c	19 Sep 2003 23:47:27 -0000
***************
*** 741,751 ****
  #ifndef __CYGWIN__
  #ifdef RLIMIT_NOFILE			/* most platform uses RLIMIT_NOFILE */
  				if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
- 				{
  #else							/* but BSD doesn't ... */
  				if (getrlimit(RLIMIT_OFILE, &rlim) == -1)
- 				{
  #endif   /* HAVE_RLIMIT_NOFILE */
  					fprintf(stderr, "getrlimit failed. reason: %s\n", strerror(errno));
  					exit(1);
  				}
--- 741,750 ----
  #ifndef __CYGWIN__
  #ifdef RLIMIT_NOFILE			/* most platform uses RLIMIT_NOFILE */
  				if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
  #else							/* but BSD doesn't ... */
  				if (getrlimit(RLIMIT_OFILE, &rlim) == -1)
  #endif   /* HAVE_RLIMIT_NOFILE */
+ 				{
  					fprintf(stderr, "getrlimit failed. reason: %s\n", strerror(errno));
  					exit(1);
  				}
Index: doc/src/sgml/indices.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/indices.sgml,v
retrieving revision 1.44
diff -c -r1.44 indices.sgml
*** doc/src/sgml/indices.sgml	11 Sep 2003 21:42:19 -0000	1.44
--- doc/src/sgml/indices.sgml	16 Sep 2003 05:32:21 -0000
***************
*** 116,127 ****
      <primary>B-tree</primary>
      <see>index</see>
     </indexterm>
!    By
!    default, the <command>CREATE INDEX</command> command will create a
!    B-tree index, which fits the most common situations.  In
!    particular, the <productname>PostgreSQL</productname> query planner
!    will consider using a B-tree index whenever an indexed column is
!    involved in a comparison using one of these operators:
  
     <simplelist type="inline">
      <member><literal>&lt;</literal></member>
--- 116,127 ----
      <primary>B-tree</primary>
      <see>index</see>
     </indexterm>
! 
!    By default, the <command>CREATE INDEX</command> command will create
!    a B-tree index, which is a good choice for many common
!    situations. In particular, the <productname>PostgreSQL</> query
!    planner will consider using a B-tree index whenever an indexed
!    column is involved in a comparison using one of these operators:
  
     <simplelist type="inline">
      <member><literal>&lt;</literal></member>
***************
*** 304,325 ****
  <synopsis>
  CREATE UNIQUE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable> (<replaceable>column</replaceable> <optional>, ...</optional>);
  </synopsis>
!    Currently, only B-tree indexes can be declared unique.
    </para>
  
    <para>
     When an index is declared unique, multiple table rows with equal
!    indexed values will not be allowed.  Null values are not considered
     equal.
    </para>
  
    <para>
     <productname>PostgreSQL</productname> automatically creates unique
!    indexes when a table is declared with a unique constraint or a
!    primary key, on the columns that make up the primary key or unique
!    columns (a multicolumn index, if appropriate), to enforce that
!    constraint.  A unique index can be added to a table at any later
!    time, to add a unique constraint.
    </para>
  
    <note>
--- 304,325 ----
  <synopsis>
  CREATE UNIQUE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable> (<replaceable>column</replaceable> <optional>, ...</optional>);
  </synopsis>
!    Currently, only B-tree and hash indexes can be declared unique.
    </para>
  
    <para>
     When an index is declared unique, multiple table rows with equal
!    indexed values will not be allowed. Null values are not considered
     equal.
    </para>
  
    <para>
     <productname>PostgreSQL</productname> automatically creates unique
!    B-tree indexes when a table is declared with a unique constraint or
!    a primary key, on the columns that make up the primary key or
!    unique columns (a multicolumn index, if appropriate), to enforce
!    that constraint.  A unique index can be added to a table at any
!    later time, to add a unique constraint.
    </para>
  
    <note>
Index: doc/src/sgml/release.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/release.sgml,v
retrieving revision 1.207
diff -c -r1.207 release.sgml
*** doc/src/sgml/release.sgml	11 Sep 2003 21:42:20 -0000	1.207
--- doc/src/sgml/release.sgml	17 Sep 2003 04:36:16 -0000
***************
*** 2328,2347 ****
         </para>
        </listitem>
       </varlistentry>
- 
- <!--
-      <varlistentry>
-       <term>
-        Upcoming Features
-       </term>
-       <listitem>
-        <para>
- 	In 7.1 or 7.2, we plan to have outer joins, storage for very long rows, and a
- 	write-ahead logging system.
-        </para>
-       </listitem>
-      </varlistentry>
- -->
      </variablelist>
     </para>
  
--- 2328,2333 ----
Index: doc/src/sgml/ref/create_index.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/ref/create_index.sgml,v
retrieving revision 1.42
diff -c -r1.42 create_index.sgml
*** doc/src/sgml/ref/create_index.sgml	11 Sep 2003 21:42:20 -0000	1.42
--- doc/src/sgml/ref/create_index.sgml	16 Sep 2003 05:28:23 -0000
***************
*** 198,205 ****
     Currently, only the B-tree and GiST index methods support
     multicolumn indexes. Up to 32 fields may be specified by default.
     (This limit can be altered when building
!    <productname>PostgreSQL</productname>.)  Only B-tree currently
!    supports unique indexes.
    </para>
  
    <para>
--- 198,205 ----
     Currently, only the B-tree and GiST index methods support
     multicolumn indexes. Up to 32 fields may be specified by default.
     (This limit can be altered when building
!    <productname>PostgreSQL</productname>.)  Only the B-tree and hash
!    index methods currently support unique indexes.
    </para>
  
    <para>
***************
*** 229,236 ****
    <title>Examples</title>
  
    <para>
!    To create a B-tree index on the column <literal>title</literal> in
!    the table <literal>films</literal>:
  <programlisting>
  CREATE UNIQUE INDEX title_idx ON films (title);
  </programlisting>
--- 229,236 ----
    <title>Examples</title>
  
    <para>
!    To create a unique B-tree index on the column
!    <literal>title</literal> in the table <literal>films</literal>:
  <programlisting>
  CREATE UNIQUE INDEX title_idx ON films (title);
  </programlisting>
Index: src/backend/access/gist/gist.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/gist/gist.c,v
retrieving revision 1.105
diff -c -r1.105 gist.c
*** src/backend/access/gist/gist.c	4 Aug 2003 02:39:56 -0000	1.105
--- src/backend/access/gist/gist.c	5 Sep 2003 04:39:28 -0000
***************
*** 186,212 ****
  
  	/* okay, all heap tuples are indexed */
  
! 	/*
! 	 * Since we just counted the tuples in the heap, we update its stats
! 	 * in pg_class to guarantee that the planner takes advantage of the
! 	 * index we just created.  But, only update statistics during normal
! 	 * index definitions, not for indices on system catalogs created
! 	 * during bootstrap processing.  We must close the relations before
! 	 * updating statistics to guarantee that the relcache entries are
! 	 * flushed when we increment the command counter in UpdateStats(). But
! 	 * we do not release any locks on the relations; those will be held
! 	 * until end of transaction.
! 	 */
! 	if (IsNormalProcessingMode())
! 	{
! 		Oid			hrelid = RelationGetRelid(heap);
! 		Oid			irelid = RelationGetRelid(index);
! 
! 		heap_close(heap, NoLock);
! 		index_close(index);
! 		UpdateStats(hrelid, reltuples);
! 		UpdateStats(irelid, buildstate.indtuples);
! 	}
  
  	freeGISTstate(&buildstate.giststate);
  #ifdef GISTDEBUG
--- 186,193 ----
  
  	/* okay, all heap tuples are indexed */
  
! 	/* update stats for new index; see notes in update_index_stats */
! 	update_index_stats(heap, index, reltuples, buildstate.indtuples);
  
  	freeGISTstate(&buildstate.giststate);
  #ifdef GISTDEBUG
Index: src/backend/access/hash/README
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/hash/README,v
retrieving revision 1.3
diff -c -r1.3 README
*** src/backend/access/hash/README	4 Sep 2003 22:06:27 -0000	1.3
--- src/backend/access/hash/README	16 Sep 2003 21:07:58 -0000
***************
*** 104,110 ****
  mapping.  A process attempting to enlarge the hash table by splitting a
  bucket must exclusive-lock this lock before modifying the metapage data
  representing the mapping.  Processes intending to access a particular
! bucket must share-lock this lock until they have acquired lock on the
  correct target bucket.
  
  LockPage(rel, page), where page is the page number of a hash bucket page,
--- 104,110 ----
  mapping.  A process attempting to enlarge the hash table by splitting a
  bucket must exclusive-lock this lock before modifying the metapage data
  representing the mapping.  Processes intending to access a particular
! bucket must share-lock this lock until they have acquired a lock on the
  correct target bucket.
  
  LockPage(rel, page), where page is the page number of a hash bucket page,
***************
*** 123,129 ****
  read or write buffer lock on the metapage or bucket page before accessing
  said page.
  
! Processes performing hash index scans must hold share lock on the bucket
  they are scanning throughout the scan.  This seems to be essential, since
  there is no reasonable way for a scan to cope with its bucket being split
  underneath it.  This creates a possibility of deadlock external to the
--- 123,129 ----
  read or write buffer lock on the metapage or bucket page before accessing
  said page.
  
! Processes performing hash index scans must hold a share lock on the bucket
  they are scanning throughout the scan.  This seems to be essential, since
  there is no reasonable way for a scan to cope with its bucket being split
  underneath it.  This creates a possibility of deadlock external to the
***************
*** 135,142 ****
  to be an lmgr lock, because as we'll see below it is held while attempting
  to acquire a bucket lock, and so it could also participate in a deadlock.
  
! Processes must obtain read (share) buffer context lock on any hash index
! page while reading it, and write (exclusive) lock while modifying it.
  To prevent deadlock we enforce these coding rules: no buffer lock may be
  held long term (across index AM calls), nor may any buffer lock be held
  while waiting for an lmgr lock, nor may more than one buffer lock
--- 135,142 ----
  to be an lmgr lock, because as we'll see below it is held while attempting
  to acquire a bucket lock, and so it could also participate in a deadlock.
  
! Processes must obtain a read (share) buffer context lock on any hash index
! page while reading it, and a write (exclusive) lock while modifying it.
  To prevent deadlock we enforce these coding rules: no buffer lock may be
  held long term (across index AM calls), nor may any buffer lock be held
  while waiting for an lmgr lock, nor may more than one buffer lock
***************
*** 161,204 ****
  
  The reader algorithm is:
  
! 	share-lock page 0 (to prevent active split)
! 	read/sharelock meta page
  	compute bucket number for target hash key
  	release meta page
! 	share-lock bucket page (to prevent split/compact of this bucket)
  	release page 0 share-lock
  -- then, per read request:
! 	read/sharelock current page of bucket
  		step to next page if necessary (no chaining of locks)
  	get tuple
  	release current page
  -- at scan shutdown:
  	release bucket share-lock
  
! By holding the page-zero lock until lock on the target bucket is obtained,
! the reader ensures that the target bucket calculation is valid (otherwise
! the bucket might be split before the reader arrives at it, and the target
! entries might go into the new bucket).  Holding the bucket sharelock for
! the remainder of the scan prevents the reader's current-tuple pointer from
! being invalidated by other processes.  Notice though that the reader need
! not prevent other buckets from being split or compacted.
  
  The insertion algorithm is rather similar:
  
! 	share-lock page 0 (to prevent active split)
! 	read/sharelock meta page
  	compute bucket number for target hash key
  	release meta page
! 	share-lock bucket page (to prevent split/compact of this bucket)
  	release page 0 share-lock
  -- (so far same as reader)
! 	read/exclusive-lock current page of bucket
  	if full, release, read/exclusive-lock next page; repeat as needed
  	>> see below if no space in any page of bucket
  	insert tuple
  	write/release current page
  	release bucket share-lock
! 	read/exclusive-lock meta page
  	increment tuple count, decide if split needed
  	write/release meta page
  	done if no split needed, else enter Split algorithm below
--- 161,205 ----
  
  The reader algorithm is:
  
! 	share-lock page 0, to prevent active split (lmgr lock)
! 	read/sharelock meta page (lwlock)
  	compute bucket number for target hash key
  	release meta page
! 	share-lock bucket, to prevent split/compact (lmgr lock)
  	release page 0 share-lock
  -- then, per read request:
! 	read/sharelock current page of bucket (lwlock)
  		step to next page if necessary (no chaining of locks)
  	get tuple
  	release current page
  -- at scan shutdown:
  	release bucket share-lock
  
! By holding the page-zero lock until the lock on the target bucket is
! obtained, the reader ensures that the target bucket calculation is
! valid (otherwise the bucket might be split before the reader arrives
! at it, and the target entries might go into the new bucket).  Holding
! the bucket sharelock for the remainder of the scan prevents the
! reader's current-tuple pointer from being invalidated by other
! processes.  Notice though that the reader need not prevent other
! buckets from being split or compacted.
  
  The insertion algorithm is rather similar:
  
! 	share-lock page 0, to prevent active split (lmgr lock)
! 	read/sharelock meta page (lwlock)
  	compute bucket number for target hash key
  	release meta page
! 	share-lock bucket, to prevent split/compact (lmgr lock)
  	release page 0 share-lock
  -- (so far same as reader)
! 	read/exclusive-lock current page of bucket (lwlock)
  	if full, release, read/exclusive-lock next page; repeat as needed
  	>> see below if no space in any page of bucket
  	insert tuple
  	write/release current page
  	release bucket share-lock
! 	read/exclusive-lock meta page (lwlock)
  	increment tuple count, decide if split needed
  	write/release meta page
  	done if no split needed, else enter Split algorithm below
***************
*** 271,286 ****
  The fourth operation is garbage collection (bulk deletion):
  
  	next bucket := 0
! 	read/sharelock meta page
  	fetch current max bucket number
  	release meta page
  	while next bucket <= max bucket do
! 		Acquire X lock on target bucket
  		Scan and remove tuples, compact free space as needed
! 		Release X lock
  		next bucket ++
  	end loop
! 	exclusive-lock meta page
  	check if number of buckets changed
  	if so, release lock and return to for-each-bucket loop
  	else update metapage tuple count
--- 272,287 ----
  The fourth operation is garbage collection (bulk deletion):
  
  	next bucket := 0
! 	read/sharelock meta page (lwlock)
  	fetch current max bucket number
  	release meta page
  	while next bucket <= max bucket do
! 		Acquire write lock on target bucket (lmgr lock)
  		Scan and remove tuples, compact free space as needed
! 		Release write lock on bucket
  		next bucket ++
  	end loop
! 	exclusive-lock meta page (lwlock)
  	check if number of buckets changed
  	if so, release lock and return to for-each-bucket loop
  	else update metapage tuple count
***************
*** 301,307 ****
  The exclusive lock request could deadlock in some strange scenarios, but
  we can just error out without any great harm being done.
  
- 
  Free space management
  ---------------------
  
--- 302,307 ----
Index: src/backend/access/hash/hash.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/hash/hash.c,v
retrieving revision 1.68
diff -c -r1.68 hash.c
*** src/backend/access/hash/hash.c	4 Sep 2003 22:06:27 -0000	1.68
--- src/backend/access/hash/hash.c	12 Sep 2003 20:32:29 -0000
***************
*** 31,36 ****
--- 31,38 ----
  typedef struct
  {
  	double		indtuples;
+ 	double      isUnique;
+ 	Relation    heapRel;
  } HashBuildState;
  
  static void hashbuildCallback(Relation index,
***************
*** 71,102 ****
  
  	/* build the index */
  	buildstate.indtuples = 0;
  
  	/* do the heap scan */
  	reltuples = IndexBuildHeapScan(heap, index, indexInfo,
  								hashbuildCallback, (void *) &buildstate);
  
! 	/*
! 	 * Since we just counted the tuples in the heap, we update its stats
! 	 * in pg_class to guarantee that the planner takes advantage of the
! 	 * index we just created.  But, only update statistics during normal
! 	 * index definitions, not for indices on system catalogs created
! 	 * during bootstrap processing.  We must close the relations before
! 	 * updating statistics to guarantee that the relcache entries are
! 	 * flushed when we increment the command counter in UpdateStats(). But
! 	 * we do not release any locks on the relations; those will be held
! 	 * until end of transaction.
! 	 */
! 	if (IsNormalProcessingMode())
! 	{
! 		Oid			hrelid = RelationGetRelid(heap);
! 		Oid			irelid = RelationGetRelid(index);
! 
! 		heap_close(heap, NoLock);
! 		index_close(index);
! 		UpdateStats(hrelid, reltuples);
! 		UpdateStats(irelid, buildstate.indtuples);
! 	}
  
  	PG_RETURN_VOID();
  }
--- 73,87 ----
  
  	/* build the index */
  	buildstate.indtuples = 0;
+ 	buildstate.isUnique  = indexInfo->ii_Unique;
+ 	buildstate.heapRel   = heap;
  
  	/* do the heap scan */
  	reltuples = IndexBuildHeapScan(heap, index, indexInfo,
  								hashbuildCallback, (void *) &buildstate);
  
! 	/* update stats for new index; see notes in update_index_stats */
! 	update_index_stats(heap, index, reltuples, buildstate.indtuples);
  
  	PG_RETURN_VOID();
  }
***************
*** 130,136 ****
  
  	hitem = _hash_formitem(itup);
  
! 	res = _hash_doinsert(index, hitem);
  
  	if (res)
  		pfree(res);
--- 115,124 ----
  
  	hitem = _hash_formitem(itup);
  
! 	res = _hash_doinsert(index,
! 						 hitem,
! 						 buildstate->isUnique,
! 						 buildstate->heapRel);
  
  	if (res)
  		pfree(res);
***************
*** 155,165 ****
  	Datum	   *datum = (Datum *) PG_GETARG_POINTER(1);
  	char	   *nulls = (char *) PG_GETARG_POINTER(2);
  	ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
- 
- #ifdef NOT_USED
  	Relation	heapRel = (Relation) PG_GETARG_POINTER(4);
  	bool		checkUnique = PG_GETARG_BOOL(5);
- #endif
  	InsertIndexResult res;
  	HashItem	hitem;
  	IndexTuple	itup;
--- 143,150 ----
***************
*** 185,191 ****
  
  	hitem = _hash_formitem(itup);
  
! 	res = _hash_doinsert(rel, hitem);
  
  	pfree(hitem);
  	pfree(itup);
--- 170,176 ----
  
  	hitem = _hash_formitem(itup);
  
! 	res = _hash_doinsert(rel, hitem, checkUnique, heapRel);
  
  	pfree(hitem);
  	pfree(itup);
Index: src/backend/access/hash/hashinsert.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/hash/hashinsert.c,v
retrieving revision 1.30
diff -c -r1.30 hashinsert.c
*** src/backend/access/hash/hashinsert.c	4 Sep 2003 22:06:27 -0000	1.30
--- src/backend/access/hash/hashinsert.c	20 Sep 2003 07:02:00 -0000
***************
*** 16,27 ****
  #include "postgres.h"
  
  #include "access/hash.h"
  #include "storage/lmgr.h"
  
  
  static OffsetNumber _hash_pgaddtup(Relation rel, Buffer buf,
  								   Size itemsize, HashItem hitem);
! 
  
  /*
   *	_hash_doinsert() -- Handle insertion of a single HashItem in the table.
--- 16,30 ----
  #include "postgres.h"
  
  #include "access/hash.h"
+ #include "access/heapam.h"
  #include "storage/lmgr.h"
  
  
  static OffsetNumber _hash_pgaddtup(Relation rel, Buffer buf,
  								   Size itemsize, HashItem hitem);
! static TransactionId _hash_check_unique(Relation indexRel, HashItem hitem,
! 										BlockNumber blkno, Relation heapRel);
! static bool _hash_isequal(IndexTuple itup, ScanKey scankey, Relation rel);
  
  /*
   *	_hash_doinsert() -- Handle insertion of a single HashItem in the table.
***************
*** 31,37 ****
   *		The datum to be used as a "key" is in the hashitem.
   */
  InsertIndexResult
! _hash_doinsert(Relation rel, HashItem hitem)
  {
  	Buffer		buf;
  	Buffer		metabuf;
--- 34,41 ----
   *		The datum to be used as a "key" is in the hashitem.
   */
  InsertIndexResult
! _hash_doinsert(Relation indexRel, HashItem hitem,
! 			   bool isUnique, Relation heapRel)
  {
  	Buffer		buf;
  	Buffer		metabuf;
***************
*** 49,65 ****
  	Bucket		bucket;
  	Datum		datum;
  	bool		isnull;
  
  	/*
  	 * Compute the hash key for the item.  We do this first so as not to
  	 * need to hold any locks while running the hash function.
  	 */
  	itup = &(hitem->hash_itup);
! 	if (rel->rd_rel->relnatts != 1)
! 		elog(ERROR, "hash indexes support only one index key");
! 	datum = index_getattr(itup, 1, RelationGetDescr(rel), &isnull);
  	Assert(!isnull);
! 	hashkey = _hash_datum2hashkey(rel, datum);
  
  	/* compute item size too */
  	itemsz = IndexTupleDSize(hitem->hash_itup)
--- 53,71 ----
  	Bucket		bucket;
  	Datum		datum;
  	bool		isnull;
+ 	LOCKMODE    bucket_lock_mode;
+ 
+ 	if (indexRel->rd_rel->relnatts != 1)
+ 		elog(ERROR, "hash indexes support only one index key");
  
  	/*
  	 * Compute the hash key for the item.  We do this first so as not to
  	 * need to hold any locks while running the hash function.
  	 */
  	itup = &(hitem->hash_itup);
! 	datum = index_getattr(itup, 1, RelationGetDescr(indexRel), &isnull);
  	Assert(!isnull);
! 	hashkey = _hash_datum2hashkey(indexRel, datum);
  
  	/* compute item size too */
  	itemsz = IndexTupleDSize(hitem->hash_itup)
***************
*** 69,95 ****
  								 * we need to be consistent */
  
  	/*
  	 * Acquire shared split lock so we can compute the target bucket
  	 * safely (see README).
  	 */
! 	_hash_getlock(rel, 0, HASH_SHARE);
  
  	/* Read the metapage */
! 	metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ);
  	metap = (HashMetaPage) BufferGetPage(metabuf);
! 	_hash_checkpage(rel, (Page) metap, LH_META_PAGE);
  
  	/*
  	 * Check whether the item can fit on a hash page at all. (Eventually,
  	 * we ought to try to apply TOAST methods if not.)  Note that at this
  	 * point, itemsz doesn't include the ItemId.
  	 */
! 	if (itemsz > HashMaxItemSize((Page) metap))
  		ereport(ERROR,
  				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  				 errmsg("index tuple size %lu exceeds hash maximum, %lu",
  						(unsigned long) itemsz,
! 						(unsigned long) HashMaxItemSize((Page) metap))));
  
  	/*
  	 * Compute the target bucket number, and convert to block number.
--- 75,110 ----
  								 * we need to be consistent */
  
  	/*
+ 	 * This defines the strength of the lmgr lock we will acquire on
+ 	 * the bucket chain we're inserting into. If this is a unique
+ 	 * index, we need to block other insertions from being made into
+ 	 * the bucket. (See 'README' for more.)
+ 	 */
+ 	bucket_lock_mode = isUnique ? HASH_UNIQUE_INSERT : HASH_SHARE;
+ 
+  top:
+ 	/*
  	 * Acquire shared split lock so we can compute the target bucket
  	 * safely (see README).
  	 */
! 	_hash_getlock(indexRel, 0, HASH_SHARE);
  
  	/* Read the metapage */
! 	metabuf = _hash_getbuf(indexRel, HASH_METAPAGE, HASH_READ);
  	metap = (HashMetaPage) BufferGetPage(metabuf);
! 	_hash_checkpage(indexRel, (Page) metap, LH_META_PAGE);
  
  	/*
  	 * Check whether the item can fit on a hash page at all. (Eventually,
  	 * we ought to try to apply TOAST methods if not.)  Note that at this
  	 * point, itemsz doesn't include the ItemId.
  	 */
! 	if (itemsz > HashMaxItemSize(metap))
  		ereport(ERROR,
  				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  				 errmsg("index tuple size %lu exceeds hash maximum, %lu",
  						(unsigned long) itemsz,
! 						(unsigned long) HashMaxItemSize(metap))));
  
  	/*
  	 * Compute the target bucket number, and convert to block number.
***************
*** 102,120 ****
  	blkno = BUCKET_TO_BLKNO(metap, bucket);
  
  	/* release lock on metapage, but keep pin since we'll need it again */
! 	_hash_chgbufaccess(rel, metabuf, HASH_READ, HASH_NOLOCK);
  
  	/*
  	 * Acquire share lock on target bucket; then we can release split lock.
  	 */
! 	_hash_getlock(rel, blkno, HASH_SHARE);
  
! 	_hash_droplock(rel, 0, HASH_SHARE);
  
  	/* Fetch the primary bucket page for the bucket */
! 	buf = _hash_getbuf(rel, blkno, HASH_WRITE);
  	page = BufferGetPage(buf);
! 	_hash_checkpage(rel, page, LH_BUCKET_PAGE);
  	pageopaque = (HashPageOpaque) PageGetSpecialPointer(page);
  	Assert(pageopaque->hasho_bucket == bucket);
  
--- 117,165 ----
  	blkno = BUCKET_TO_BLKNO(metap, bucket);
  
  	/* release lock on metapage, but keep pin since we'll need it again */
! 	_hash_chgbufaccess(indexRel, metabuf, HASH_READ, HASH_NOLOCK);
  
  	/*
  	 * Acquire share lock on target bucket; then we can release split lock.
  	 */
! 	_hash_getlock(indexRel, blkno, bucket_lock_mode);
  
! 	_hash_droplock(indexRel, 0, HASH_SHARE);
! 
! 	/*
! 	 * If this is a unique index, check for duplicates. By definition,
! 	 * if a duplicate exists, it must be in the same bucket chain as
! 	 * the tuple we're in the process of inserting. In order to
! 	 * prevent concurrent insertion of duplicate tuples into this
! 	 * bucket chain, we've already acquired the "right of insertion"
! 	 * lmgr lock (which is only released once we've actually added the
! 	 * new tuple to the disk).
! 	 */
! 	if (isUnique)
! 	{
! 		TransactionId xwait;
! 
! 		xwait = _hash_check_unique(indexRel, hitem, blkno, heapRel);
! 
! 		if (TransactionIdIsValid(xwait))
! 		{
! 			/*
! 			 * We need to wait for another txn to commit; that might
! 			 * take a while, so we drop all our locks, do the wait, and
! 			 * then start again
! 			 */
! 
! 			_hash_droplock(indexRel, blkno, bucket_lock_mode);
! 			_hash_dropbuf(indexRel, metabuf);
! 			XactLockTableWait(xwait);
! 			goto top;
! 		}
! 	}
  
  	/* Fetch the primary bucket page for the bucket */
! 	buf = _hash_getbuf(indexRel, blkno, HASH_WRITE);
  	page = BufferGetPage(buf);
! 	_hash_checkpage(indexRel, page, LH_BUCKET_PAGE);
  	pageopaque = (HashPageOpaque) PageGetSpecialPointer(page);
  	Assert(pageopaque->hasho_bucket == bucket);
  
***************
*** 129,139 ****
  		if (BlockNumberIsValid(nextblkno))
  		{
  			/*
! 			 * ovfl page exists; go get it.  if it doesn't have room,
! 			 * we'll find out next pass through the loop test above.
  			 */
! 			_hash_relbuf(rel, buf);
! 			buf = _hash_getbuf(rel, nextblkno, HASH_WRITE);
  			page = BufferGetPage(buf);
  		}
  		else
--- 174,185 ----
  		if (BlockNumberIsValid(nextblkno))
  		{
  			/*
! 			 * overflow page exists; go get it.  if it doesn't have
! 			 * room, we'll find out in the next pass through the loop
! 			 * test above.
  			 */
! 			_hash_relbuf(indexRel, buf);
! 			buf = _hash_getbuf(indexRel, nextblkno, HASH_WRITE);
  			page = BufferGetPage(buf);
  		}
  		else
***************
*** 143,209 ****
  			 * page with enough room.  allocate a new overflow page.
  			 */
  
! 			/* release our write lock without modifying buffer */
! 			_hash_chgbufaccess(rel, buf, HASH_READ, HASH_NOLOCK);
  
  			/* chain to a new overflow page */
! 			buf = _hash_addovflpage(rel, metabuf, buf);
  			page = BufferGetPage(buf);
  
  			/* should fit now, given test above */
  			Assert(PageGetFreeSpace(page) >= itemsz);
  		}
! 		_hash_checkpage(rel, page, LH_OVERFLOW_PAGE);
  		pageopaque = (HashPageOpaque) PageGetSpecialPointer(page);
  		Assert(pageopaque->hasho_bucket == bucket);
  	}
  
  	/* found page with enough space, so add the item here */
! 	itup_off = _hash_pgaddtup(rel, buf, itemsz, hitem);
  	itup_blkno = BufferGetBlockNumber(buf);
  
  	/* write and release the modified page */
! 	_hash_wrtbuf(rel, buf);
  
  	/* We can drop the bucket lock now */
! 	_hash_droplock(rel, blkno, HASH_SHARE);
  
  	/*
  	 * Write-lock the metapage so we can increment the tuple count.
  	 * After incrementing it, check to see if it's time for a split.
  	 */
! 	_hash_chgbufaccess(rel, metabuf, HASH_NOLOCK, HASH_WRITE);
  
  	metap->hashm_ntuples += 1;
  
! 	/* Make sure this stays in sync with _hash_expandtable() */
! 	do_expand = metap->hashm_ntuples >
! 		(double) metap->hashm_ffactor * (metap->hashm_maxbucket + 1);
  
  	/* Write out the metapage and drop lock, but keep pin */
! 	_hash_chgbufaccess(rel, metabuf, HASH_WRITE, HASH_NOLOCK);
  
  	/* Attempt to split if a split is needed */
  	if (do_expand)
! 		_hash_expandtable(rel, metabuf);
  
  	/* Finally drop our pin on the metapage */
! 	_hash_dropbuf(rel, metabuf);
  
  	/* Create the return data structure */
  	res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData));
- 
  	ItemPointerSet(&(res->pointerData), itup_blkno, itup_off);
- 
  	return res;
  }
  
  /*
!  *	_hash_pgaddtup() -- add a tuple to a particular page in the index.
   *
   *		This routine adds the tuple to the page as requested; it does
!  *		not write out the page.  It is an error to call pgaddtup() without
!  *		a write lock and pin.
   */
  static OffsetNumber
  _hash_pgaddtup(Relation rel,
--- 189,256 ----
  			 * page with enough room.  allocate a new overflow page.
  			 */
  
! 			/*
! 			 * release our write lock but keep the buffer pinned;
! 			 * since we didn't modify the buffer, we specify
! 			 * HASH_READ, so that the buffer is not marked dirty
! 			 */
! 			_hash_chgbufaccess(indexRel, buf, HASH_READ, HASH_NOLOCK);
  
  			/* chain to a new overflow page */
! 			buf = _hash_addovflpage(indexRel, metabuf, buf);
  			page = BufferGetPage(buf);
  
  			/* should fit now, given test above */
  			Assert(PageGetFreeSpace(page) >= itemsz);
  		}
! 
! 		_hash_checkpage(indexRel, page, LH_OVERFLOW_PAGE);
  		pageopaque = (HashPageOpaque) PageGetSpecialPointer(page);
  		Assert(pageopaque->hasho_bucket == bucket);
  	}
  
  	/* found page with enough space, so add the item here */
! 	itup_off = _hash_pgaddtup(indexRel, buf, itemsz, hitem);
  	itup_blkno = BufferGetBlockNumber(buf);
  
  	/* write and release the modified page */
! 	_hash_wrtbuf(indexRel, buf);
  
  	/* We can drop the bucket lock now */
! 	_hash_droplock(indexRel, blkno, bucket_lock_mode);
  
  	/*
  	 * Write-lock the metapage so we can increment the tuple count.
  	 * After incrementing it, check to see if it's time for a split.
  	 */
! 	_hash_chgbufaccess(indexRel, metabuf, HASH_NOLOCK, HASH_WRITE);
  
  	metap->hashm_ntuples += 1;
  
! 	do_expand = _hash_need_split(metap);
  
  	/* Write out the metapage and drop lock, but keep pin */
! 	_hash_chgbufaccess(indexRel, metabuf, HASH_WRITE, HASH_NOLOCK);
  
  	/* Attempt to split if a split is needed */
  	if (do_expand)
! 		_hash_expandtable(indexRel, metabuf);
  
  	/* Finally drop our pin on the metapage */
! 	_hash_dropbuf(indexRel, metabuf);
  
  	/* Create the return data structure */
  	res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData));
  	ItemPointerSet(&(res->pointerData), itup_blkno, itup_off);
  	return res;
  }
  
  /*
!  *	_hash_pgaddtup() -- Add a tuple to a particular page in the index.
   *
   *		This routine adds the tuple to the page as requested; it does
!  *		not write out the page.  It is an error to call _hash_pgaddtup()
!  *		without a write lock and pin.
   */
  static OffsetNumber
  _hash_pgaddtup(Relation rel,
***************
*** 214,219 ****
--- 261,267 ----
  	OffsetNumber itup_off;
  	Page		page;
  
+ 	Assert(BufferIsPinned(buf));
  	page = BufferGetPage(buf);
  	_hash_checkpage(rel, page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
  
***************
*** 225,227 ****
--- 273,419 ----
  
  	return itup_off;
  }
+ 
+ /*
+  * _hash_check_unique() -- Check for violation of unique index
+  * constraint
+  *
+  * If a duplicate is found, this reports an error via ereport(), and
+  * doesn't return. It returns InvalidTransactionId if there is no
+  * conflict, or a valid xact ID if there is another xact we need to wait
+  * for to see if it commits a conflicting tuple.
+  *
+  * On entry, 'blkno' is the block number of the primary bucket page --
+  * it should not be locked or pinned. This backend should hold the
+  * "right of insertion" lmgr lock for this bucket.
+  *
+  * On exit, the lmgr ROI lock is still held, and no pins or locks are
+  * held on the pages in the bucket chain.
+  */
+ static TransactionId
+ _hash_check_unique(Relation indexRel, HashItem hitem,
+ 				   BlockNumber blkno, Relation heapRel)
+ {
+ 	OffsetNumber	offset;
+ 	OffsetNumber	maxoff;
+ 	Buffer          buf;
+ 	Page			page;
+ 	HashPageOpaque	opaque;
+ 	ScanKey         scankey;
+ 	TransactionId   xid = InvalidTransactionId;
+ 
+ 	scankey = _hash_mkscankey(indexRel, &hitem->hash_itup);
+ 
+ 	/* NULL is never equal to anything */
+ 	if (scankey->sk_flags & SK_ISNULL)
+ 	{
+ 		_hash_freeskey(scankey);
+ 		return xid;
+ 	}
+ 
+ 	/*
+ 	 * Fetch the primary bucket page. We only need a read lock since
+ 	 * the ROI lmgr lock we already hold prevents concurrent
+ 	 * insertions into this bucket chain.
+ 	 */
+ 	buf = _hash_getbuf(indexRel, blkno, HASH_READ);
+ 	page = BufferGetPage(buf);
+ 	_hash_checkpage(indexRel, page, LH_BUCKET_PAGE);
+ 	opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+ 	offset = FirstOffsetNumber;
+ 	maxoff = PageGetMaxOffsetNumber(page);
+ 
+ 	for (;;)
+ 	{
+ 		HashItem   cur_hitem;
+ 		IndexTuple itup;
+ 		ItemId     item_id;
+ 
+ 		if (offset > maxoff)
+ 		{
+ 			/*
+ 			 * we fell off the end of the page, so go to the next
+ 			 * overflow page if one exists
+ 			 */
+ 			_hash_readnext(indexRel, &buf, &page, &opaque);
+ 			
+ 			if (!BufferIsValid(buf)) /* no more overflow pages */
+ 				break;
+ 
+ 			Assert(BufferIsPinned(buf));
+ 			offset = FirstOffsetNumber;
+ 			maxoff = PageGetMaxOffsetNumber(page);
+ 		}
+ 
+ 		Assert(!TransactionIdIsValid(xid));
+ 		Assert(OffsetNumberIsValid(offset));
+ 
+ 		item_id = PageGetItemId(page, offset);
+ 		cur_hitem = (HashItem) PageGetItem(page, item_id);
+ 		itup = &cur_hitem->hash_itup;
+ 
+ 		if (_hash_isequal(itup, scankey, indexRel))
+ 		{
+ 			/* fetch the heap tuple */
+ 			HeapTupleData htup;
+ 			Buffer hbuffer;
+ 
+ 			htup.t_self = itup->t_tid;
+ 
+ 			if (heap_fetch(heapRel, SnapshotDirty, &htup, &hbuffer, false, NULL))
+ 			{
+ 				/* heap tuple found & is visible: possible dup */
+ 				ReleaseBuffer(hbuffer);
+ 
+ 				xid = TransactionIdIsValid(SnapshotDirty->xmin) ?
+ 					SnapshotDirty->xmin : SnapshotDirty->xmax;
+ 
+ 				/* we need to wait on a txn, so return the xid to caller */
+ 				if (TransactionIdIsValid(xid))
+ 					break;
+ 
+ 				/* definite conflict */
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNIQUE_VIOLATION),
+ 						 errmsg("duplicate key violates UNIQUE constraint \"%s\"",
+ 								RelationGetRelationName(indexRel))));
+ 			}
+ 
+ 			Assert(!BufferIsValid(hbuffer));
+ 		}
+ 
+ 		/* advance to the next tuple in the bucket */
+ 		offset = OffsetNumberNext(offset);
+ 	}
+ 
+ 	_hash_freeskey(scankey);
+ 	return xid;
+ }
+ 
+ /*
+  * _hash_isequal() -- Returns true iff the specified IndexTuple
+  * matches the specified ScanKey.
+  */
+ static bool
+ _hash_isequal(IndexTuple itup, ScanKey scankey, Relation indexRel)
+ {
+ 	Datum datum;
+ 	bool  isnull;
+ 	bool  result;
+ 
+ 	Assert(!(scankey->sk_flags & SK_ISNULL));
+ 	Assert(scankey->sk_attno == (AttrNumber) 1);
+ 
+ 	datum = index_getattr(itup, scankey->sk_attno,
+ 						  RelationGetDescr(indexRel), &isnull);
+ 
+ 	/* NULL is not equal to anything */
+ 	if (isnull)
+ 		return false;
+ 
+ 	result = DatumGetBool(FunctionCall2(&scankey->sk_func,
+ 										 scankey->sk_argument,
+ 										 datum));
+ 
+ 	return result;
+ }
Index: src/backend/access/hash/hashovfl.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/hash/hashovfl.c,v
retrieving revision 1.41
diff -c -r1.41 hashovfl.c
*** src/backend/access/hash/hashovfl.c	4 Sep 2003 22:06:27 -0000	1.41
--- src/backend/access/hash/hashovfl.c	20 Sep 2003 07:30:39 -0000
***************
*** 75,95 ****
  }
  
  /*
!  *	_hash_addovflpage
   *
!  *	Add an overflow page to the bucket whose last page is pointed to by 'buf'.
   *
!  *	On entry, the caller must hold a pin but no lock on 'buf'.  The pin is
!  *	dropped before exiting (we assume the caller is not interested in 'buf'
!  *	anymore).  The returned overflow page will be pinned and write-locked;
!  *	it is guaranteed to be empty.
   *
!  *	The caller must hold a pin, but no lock, on the metapage buffer.
!  *	That buffer is returned in the same state.
   *
!  *	The caller must hold at least share lock on the bucket, to ensure that
!  *	no one else tries to compact the bucket meanwhile.  This guarantees that
!  *	'buf' won't stop being part of the bucket while it's unlocked.
   *
   * NB: since this could be executed concurrently by multiple processes,
   * one should not assume that the returned overflow page will be the
--- 75,95 ----
  }
  
  /*
!  * _hash_addovflpage
   *
!  * Add an overflow page to the bucket whose last page is pointed to by 'buf'.
   *
!  * On entry, the caller must hold a pin but no lock on 'buf'.  The pin is
!  * dropped before exiting (we assume the caller is not interested in 'buf'
!  * anymore).  The returned overflow page will be pinned and write-locked;
!  * it is guaranteed to be empty.
   *
!  * The caller must hold a pin, but no lock, on the metapage buffer.
!  * That buffer is returned in the same state.
   *
!  * The caller must hold at least share lock on the bucket, to ensure that
!  * no one else tries to compact the bucket meanwhile.  This guarantees that
!  * 'buf' won't stop being part of the bucket while it's unlocked.
   *
   * NB: since this could be executed concurrently by multiple processes,
   * one should not assume that the returned overflow page will be the
***************
*** 140,151 ****
  
  	/* now that we have correct backlink, initialize new overflow page */
  	_hash_pageinit(ovflpage, BufferGetPageSize(ovflbuf));
! 	ovflopaque = (HashPageOpaque) PageGetSpecialPointer(ovflpage);
  	ovflopaque->hasho_prevblkno = BufferGetBlockNumber(buf);
- 	ovflopaque->hasho_nextblkno = InvalidBlockNumber;
- 	ovflopaque->hasho_bucket = pageopaque->hasho_bucket;
- 	ovflopaque->hasho_flag = LH_OVERFLOW_PAGE;
- 	ovflopaque->hasho_filler = HASHO_FILL;
  	_hash_wrtnorelbuf(rel, ovflbuf);
  
  	/* logically chain overflow page to previous page */
--- 140,149 ----
  
  	/* now that we have correct backlink, initialize new overflow page */
  	_hash_pageinit(ovflpage, BufferGetPageSize(ovflbuf));
! 	ovflopaque = _hash_page_opaque_init(ovflpage,
! 										pageopaque->hasho_bucket,
! 										LH_OVERFLOW_PAGE);
  	ovflopaque->hasho_prevblkno = BufferGetBlockNumber(buf);
  	_hash_wrtnorelbuf(rel, ovflbuf);
  
  	/* logically chain overflow page to previous page */
Index: src/backend/access/hash/hashpage.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/hash/hashpage.c,v
retrieving revision 1.42
diff -c -r1.42 hashpage.c
*** src/backend/access/hash/hashpage.c	4 Sep 2003 22:06:27 -0000	1.42
--- src/backend/access/hash/hashpage.c	20 Sep 2003 07:30:27 -0000
***************
*** 61,67 ****
   * block number of a bucket's primary bucket page to acquire the per-bucket
   * lock.  (See README for details of the use of these locks.)
   *
!  * 'access' must be HASH_SHARE or HASH_EXCLUSIVE.
   */
  void
  _hash_getlock(Relation rel, BlockNumber whichlock, int access)
--- 61,67 ----
   * block number of a bucket's primary bucket page to acquire the per-bucket
   * lock.  (See README for details of the use of these locks.)
   *
!  * 'access' must be HASH_SHARE, HASH_UNIQUE_INSERT, or HASH_EXCLUSIVE.
   */
  void
  _hash_getlock(Relation rel, BlockNumber whichlock, int access)
***************
*** 113,120 ****
  {
  	Buffer		buf;
  
! 	if (blkno == P_NEW)
! 		elog(ERROR, "hash AM does not use P_NEW");
  
  	buf = ReadBuffer(rel, blkno);
  
--- 113,119 ----
  {
  	Buffer		buf;
  
! 	Assert(blkno != P_NEW);
  
  	buf = ReadBuffer(rel, blkno);
  
***************
*** 226,232 ****
  _hash_metapinit(Relation rel)
  {
  	HashMetaPage metap;
- 	HashPageOpaque pageopaque;
  	Buffer		metabuf;
  	Buffer		buf;
  	Page		pg;
--- 225,230 ----
***************
*** 258,270 ****
  	metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_WRITE);
  	pg = BufferGetPage(metabuf);
  	_hash_pageinit(pg, BufferGetPageSize(metabuf));
! 
! 	pageopaque = (HashPageOpaque) PageGetSpecialPointer(pg);
! 	pageopaque->hasho_prevblkno = InvalidBlockNumber;
! 	pageopaque->hasho_nextblkno = InvalidBlockNumber;
! 	pageopaque->hasho_bucket = -1;
! 	pageopaque->hasho_flag = LH_META_PAGE;
! 	pageopaque->hasho_filler = HASHO_FILL;
  
  	metap = (HashMetaPage) pg;
  
--- 256,262 ----
  	metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_WRITE);
  	pg = BufferGetPage(metabuf);
  	_hash_pageinit(pg, BufferGetPageSize(metabuf));
! 	_hash_page_opaque_init(pg, -1, LH_META_PAGE);
  
  	metap = (HashMetaPage) pg;
  
***************
*** 311,322 ****
  		buf = _hash_getbuf(rel, BUCKET_TO_BLKNO(metap, i), HASH_WRITE);
  		pg = BufferGetPage(buf);
  		_hash_pageinit(pg, BufferGetPageSize(buf));
! 		pageopaque = (HashPageOpaque) PageGetSpecialPointer(pg);
! 		pageopaque->hasho_prevblkno = InvalidBlockNumber;
! 		pageopaque->hasho_nextblkno = InvalidBlockNumber;
! 		pageopaque->hasho_bucket = i;
! 		pageopaque->hasho_flag = LH_BUCKET_PAGE;
! 		pageopaque->hasho_filler = HASHO_FILL;
  		_hash_wrtbuf(rel, buf);
  	}
  
--- 303,309 ----
  		buf = _hash_getbuf(rel, BUCKET_TO_BLKNO(metap, i), HASH_WRITE);
  		pg = BufferGetPage(buf);
  		_hash_pageinit(pg, BufferGetPageSize(buf));
! 		_hash_page_opaque_init(pg, i, LH_BUCKET_PAGE);
  		_hash_wrtbuf(rel, buf);
  	}
  
***************
*** 341,346 ****
--- 328,352 ----
  }
  
  /*
+  * _hash_page_opaque_init() -- Initialize the "opaque" section of a
+  * new hash index page.
+  */
+ HashPageOpaque 
+ _hash_page_opaque_init(Page page, Bucket bucket, uint16 flag)
+ {
+ 	HashPageOpaque opaque;
+ 
+ 	opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+ 	opaque->hasho_prevblkno = InvalidBlockNumber;
+ 	opaque->hasho_nextblkno = InvalidBlockNumber;
+ 	opaque->hasho_bucket    = bucket;
+ 	opaque->hasho_flag      = flag;
+ 	opaque->hasho_filler    = HASHO_FILL;
+ 
+ 	return opaque;
+ }
+ 
+ /*
   * Attempt to expand the hash table by creating one new bucket.
   *
   * This will silently do nothing if it cannot get the needed locks.
***************
*** 387,397 ****
  	/*
  	 * Check to see if split is still needed; someone else might have already
  	 * done one while we waited for the lock.
- 	 *
- 	 * Make sure this stays in sync with_hash_doinsert()
  	 */
! 	if (metap->hashm_ntuples <=
! 		(double) metap->hashm_ffactor * (metap->hashm_maxbucket + 1))
  		goto fail;
  
  	/*
--- 393,400 ----
  	/*
  	 * Check to see if split is still needed; someone else might have already
  	 * done one while we waited for the lock.
  	 */
! 	if (!_hash_need_split(metap))
  		goto fail;
  
  	/*
***************
*** 529,535 ****
  	Datum		datum;
  	HashItem	hitem;
  	HashPageOpaque oopaque;
- 	HashPageOpaque nopaque;
  	IndexTuple	itup;
  	Size		itemsz;
  	OffsetNumber ooffnum;
--- 532,537 ----
***************
*** 556,567 ****
  
  	/* initialize the new bucket's primary page */
  	_hash_pageinit(npage, BufferGetPageSize(nbuf));
! 	nopaque = (HashPageOpaque) PageGetSpecialPointer(npage);
! 	nopaque->hasho_prevblkno = InvalidBlockNumber;
! 	nopaque->hasho_nextblkno = InvalidBlockNumber;
! 	nopaque->hasho_bucket = nbucket;
! 	nopaque->hasho_flag = LH_BUCKET_PAGE;
! 	nopaque->hasho_filler = HASHO_FILL;
  
  	/*
  	 * Partition the tuples in the old bucket between the old bucket and the
--- 558,564 ----
  
  	/* initialize the new bucket's primary page */
  	_hash_pageinit(npage, BufferGetPageSize(nbuf));
! 	_hash_page_opaque_init(npage, nbucket, LH_BUCKET_PAGE);
  
  	/*
  	 * Partition the tuples in the old bucket between the old bucket and the
***************
*** 634,640 ****
  				nbuf = _hash_addovflpage(rel, metabuf, nbuf);
  				npage = BufferGetPage(nbuf);
  				_hash_checkpage(rel, npage, LH_OVERFLOW_PAGE);
- 				/* we don't need nopaque within the loop */
  			}
  
  			noffnum = OffsetNumberNext(PageGetMaxOffsetNumber(npage));
--- 631,636 ----
Index: src/backend/access/hash/hashsearch.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/hash/hashsearch.c,v
retrieving revision 1.34
diff -c -r1.34 hashsearch.c
*** src/backend/access/hash/hashsearch.c	4 Sep 2003 22:06:27 -0000	1.34
--- src/backend/access/hash/hashsearch.c	15 Sep 2003 16:17:02 -0000
***************
*** 62,116 ****
  }
  
  /*
-  * Advance to next page in a bucket, if any.
-  */
- static void
- _hash_readnext(Relation rel,
- 			   Buffer *bufp, Page *pagep, HashPageOpaque *opaquep)
- {
- 	BlockNumber blkno;
- 
- 	blkno = (*opaquep)->hasho_nextblkno;
- 	_hash_relbuf(rel, *bufp);
- 	*bufp = InvalidBuffer;
- 	if (BlockNumberIsValid(blkno))
- 	{
- 		*bufp = _hash_getbuf(rel, blkno, HASH_READ);
- 		*pagep = BufferGetPage(*bufp);
- 		_hash_checkpage(rel, *pagep, LH_OVERFLOW_PAGE);
- 		*opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep);
- 	}
- }
- 
- /*
-  * Advance to previous page in a bucket, if any.
-  */
- static void
- _hash_readprev(Relation rel,
- 			   Buffer *bufp, Page *pagep, HashPageOpaque *opaquep)
- {
- 	BlockNumber blkno;
- 
- 	blkno = (*opaquep)->hasho_prevblkno;
- 	_hash_relbuf(rel, *bufp);
- 	*bufp = InvalidBuffer;
- 	if (BlockNumberIsValid(blkno))
- 	{
- 		*bufp = _hash_getbuf(rel, blkno, HASH_READ);
- 		*pagep = BufferGetPage(*bufp);
- 		_hash_checkpage(rel, *pagep, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
- 		*opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep);
- 	}
- }
- 
- /*
   *	_hash_first() -- Find the first item in a scan.
   *
!  *		Find the first item in the index that
!  *		satisfies the qualification associated with the scan descriptor. On
!  *		success, the page containing the current index tuple is read locked
!  *		and pinned, and the scan's opaque data entry is updated to
!  *		include the buffer.
   */
  bool
  _hash_first(IndexScanDesc scan, ScanDirection dir)
--- 62,74 ----
  }
  
  /*
   *	_hash_first() -- Find the first item in a scan.
   *
!  *		Find the first item in the index that satisfies the
!  *		qualification associated with the scan descriptor. On success,
!  *		the page containing the current index tuple is read locked and
!  *		pinned, and the scan's opaque data entry is updated to include
!  *		the buffer.
   */
  bool
  _hash_first(IndexScanDesc scan, ScanDirection dir)
Index: src/backend/access/hash/hashutil.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/hash/hashutil.c,v
retrieving revision 1.36
diff -c -r1.36 hashutil.c
*** src/backend/access/hash/hashutil.c	4 Sep 2003 22:06:27 -0000	1.36
--- src/backend/access/hash/hashutil.c	16 Sep 2003 02:44:58 -0000
***************
*** 17,22 ****
--- 17,23 ----
  #include "access/genam.h"
  #include "access/hash.h"
  #include "access/iqual.h"
+ #include "access/istrat.h"
  
  
  /*
***************
*** 69,75 ****
  {
  	FmgrInfo   *procinfo;
  
! 	/* XXX assumes index has only one attribute */
  	procinfo = index_getprocinfo(rel, 1, HASHPROC);
  
  	return DatumGetUInt32(FunctionCall1(procinfo, key));
--- 70,76 ----
  {
  	FmgrInfo   *procinfo;
  
! 	Assert(rel->rd_rel->relnatts == 1);
  	procinfo = index_getprocinfo(rel, 1, HASHPROC);
  
  	return DatumGetUInt32(FunctionCall1(procinfo, key));
***************
*** 151,153 ****
--- 152,251 ----
  	}
  #endif   /* USE_ASSERT_CHECKING */
  }
+ 
+ /*
+  * Returns true if we should expand the hash table by adding more
+  * buckets
+  */
+ bool
+ _hash_need_split(HashMetaPage metap)
+ {
+ 	return metap->hashm_ntuples >
+ 		((double) metap->hashm_ffactor * (metap->hashm_maxbucket + 1));
+ }
+ 
+ /*
+  * This closely follows some similar code in btree (with the exception
+  * of the simplifying assumption that we're scanning for a single key
+  * only), but since this area of the code is such a performance hotspot,
+  * it is probably not worth trying to refactor it.
+  */
+ ScanKey
+ _hash_mkscankey(Relation rel, IndexTuple itup)
+ {
+ 	ScanKey		skey;
+ 	ScanKey     strat_skey;
+ 	StrategyMap strategy_map;
+ 	Datum		arg;
+ 	bool		isnull;
+ 
+ 	Assert(rel->rd_rel->relnatts == 1);
+ 
+ 	strategy_map = IndexStrategyGetStrategyMap(rel->rd_istrat,
+ 											   HTMaxStrategyNumber,
+ 											   1);
+ 	strat_skey = StrategyMapGetScanKeyEntry(strategy_map,
+ 											HTEqualStrategyNumber);
+ 
+ 	skey = palloc(sizeof(ScanKeyData));
+ 	arg = index_getattr(itup, 1, RelationGetDescr(rel), &isnull);
+ 	ScanKeyEntryInitialize(skey,
+ 						   0x0,
+ 						   (AttrNumber) 1,
+ 						   strat_skey->sk_procedure,
+ 						   arg);
+ 
+ 	return skey;
+ }
+ 
+ void
+ _hash_freeskey(ScanKey skey)
+ {
+ 	pfree(skey);
+ }
+ 
+ /*
+  * Advance to next page in a bucket, if any. If no next page exists,
+  * 'bufp' is set to InvalidBuffer, and 'pagep' and 'opaquep' are
+  * undefined.
+  */
+ void
+ _hash_readnext(Relation rel, Buffer *bufp,
+ 			   Page *pagep, HashPageOpaque *opaquep)
+ {
+ 	BlockNumber blkno;
+ 
+ 	blkno = (*opaquep)->hasho_nextblkno;
+ 	_hash_relbuf(rel, *bufp);
+ 	*bufp = InvalidBuffer;
+ 	if (BlockNumberIsValid(blkno))
+ 	{
+ 		*bufp = _hash_getbuf(rel, blkno, HASH_READ);
+ 		*pagep = BufferGetPage(*bufp);
+ 		_hash_checkpage(rel, *pagep, LH_OVERFLOW_PAGE);
+ 		*opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep);
+ 	}
+ }
+ 
+ /*
+  * Advance to previous page in a bucket, if any. If no previous page
+  * exists, 'bufp' is set to InvalidBuffer, and 'pagep' and 'opaquep' are
+  * undefined.
+  */
+ void
+ _hash_readprev(Relation rel, Buffer *bufp,
+ 			   Page *pagep, HashPageOpaque *opaquep)
+ {
+ 	BlockNumber blkno;
+ 
+ 	blkno = (*opaquep)->hasho_prevblkno;
+ 	_hash_relbuf(rel, *bufp);
+ 	*bufp = InvalidBuffer;
+ 	if (BlockNumberIsValid(blkno))
+ 	{
+ 		*bufp = _hash_getbuf(rel, blkno, HASH_READ);
+ 		*pagep = BufferGetPage(*bufp);
+ 		_hash_checkpage(rel, *pagep, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
+ 		*opaquep = (HashPageOpaque) PageGetSpecialPointer(*pagep);
+ 	}
+ }
Index: src/backend/access/index/indexam.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/index/indexam.c,v
retrieving revision 1.70
diff -c -r1.70 indexam.c
*** src/backend/access/index/indexam.c	8 Aug 2003 21:41:25 -0000	1.70
--- src/backend/access/index/indexam.c	15 Sep 2003 21:33:25 -0000
***************
*** 721,727 ****
   *		support procs in the relcache.
   * ----------------
   */
! struct FmgrInfo *
  index_getprocinfo(Relation irel,
  				  AttrNumber attnum,
  				  uint16 procnum)
--- 721,727 ----
   *		support procs in the relcache.
   * ----------------
   */
! FmgrInfo *
  index_getprocinfo(Relation irel,
  				  AttrNumber attnum,
  				  uint16 procnum)
Index: src/backend/access/nbtree/nbtinsert.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/nbtree/nbtinsert.c,v
retrieving revision 1.105
diff -c -r1.105 nbtinsert.c
*** src/backend/access/nbtree/nbtinsert.c	2 Sep 2003 22:10:16 -0000	1.105
--- src/backend/access/nbtree/nbtinsert.c	12 Sep 2003 20:53:17 -0000
***************
*** 149,159 ****
  }
  
  /*
!  *	_bt_check_unique() -- Check for violation of unique index constraint
   *
!  * Returns InvalidTransactionId if there is no conflict, else an xact ID
!  * we must wait for to see if it commits a conflicting tuple.	If an actual
!  * conflict is detected, no return --- just ereport().
   */
  static TransactionId
  _bt_check_unique(Relation rel, BTItem btitem, Relation heapRel,
--- 149,160 ----
  }
  
  /*
!  * _bt_check_unique() -- Check for violation of unique index constraint
   *
!  * If a duplicate is found, this reports an error via ereport(), and
!  * doesn't return. It returns InvalidTransactionId if there is no
!  * conflict, or a valid xact ID if there is another xact we need to wait
!  * for to see if it commits a conflicting tuple.
   */
  static TransactionId
  _bt_check_unique(Relation rel, BTItem btitem, Relation heapRel,
***************
*** 227,239 ****
  				if (heap_fetch(heapRel, SnapshotDirty, &htup, &hbuffer,
  							   true, NULL))
  				{
! 					/* it is a duplicate */
! 					TransactionId xwait =
! 					(TransactionIdIsValid(SnapshotDirty->xmin)) ?
! 					SnapshotDirty->xmin : SnapshotDirty->xmax;
  
  					ReleaseBuffer(hbuffer);
  
  					/*
  					 * If this tuple is being updated by other transaction
  					 * then we have to wait for its commit/abort.
--- 228,241 ----
  				if (heap_fetch(heapRel, SnapshotDirty, &htup, &hbuffer,
  							   true, NULL))
  				{
! 					TransactionId xwait;
  
+ 					/* it is a duplicate */
  					ReleaseBuffer(hbuffer);
  
+ 					xwait = (TransactionIdIsValid(SnapshotDirty->xmin)) ?
+ 						SnapshotDirty->xmin : SnapshotDirty->xmax;
+ 
  					/*
  					 * If this tuple is being updated by other transaction
  					 * then we have to wait for its commit/abort.
Index: src/backend/access/nbtree/nbtree.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/nbtree/nbtree.c,v
retrieving revision 1.105
diff -c -r1.105 nbtree.c
*** src/backend/access/nbtree/nbtree.c	4 Aug 2003 02:39:57 -0000	1.105
--- src/backend/access/nbtree/nbtree.c	5 Sep 2003 04:38:57 -0000
***************
*** 158,184 ****
  	}
  #endif   /* BTREE_BUILD_STATS */
  
! 	/*
! 	 * Since we just counted the tuples in the heap, we update its stats
! 	 * in pg_class to guarantee that the planner takes advantage of the
! 	 * index we just created.  But, only update statistics during normal
! 	 * index definitions, not for indices on system catalogs created
! 	 * during bootstrap processing.  We must close the relations before
! 	 * updating statistics to guarantee that the relcache entries are
! 	 * flushed when we increment the command counter in UpdateStats(). But
! 	 * we do not release any locks on the relations; those will be held
! 	 * until end of transaction.
! 	 */
! 	if (IsNormalProcessingMode())
! 	{
! 		Oid			hrelid = RelationGetRelid(heap);
! 		Oid			irelid = RelationGetRelid(index);
! 
! 		heap_close(heap, NoLock);
! 		index_close(index);
! 		UpdateStats(hrelid, reltuples);
! 		UpdateStats(irelid, buildstate.indtuples);
! 	}
  
  	PG_RETURN_VOID();
  }
--- 158,165 ----
  	}
  #endif   /* BTREE_BUILD_STATS */
  
! 	/* update stats for new index; see notes in update_index_stats */
! 	update_index_stats(heap, index, reltuples, buildstate.indtuples);
  
  	PG_RETURN_VOID();
  }
Index: src/backend/access/rtree/rtree.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/rtree/rtree.c,v
retrieving revision 1.79
diff -c -r1.79 rtree.c
*** src/backend/access/rtree/rtree.c	4 Aug 2003 02:39:57 -0000	1.79
--- src/backend/access/rtree/rtree.c	5 Sep 2003 04:39:48 -0000
***************
*** 142,168 ****
  
  	/* okay, all heap tuples are indexed */
  
! 	/*
! 	 * Since we just counted the tuples in the heap, we update its stats
! 	 * in pg_class to guarantee that the planner takes advantage of the
! 	 * index we just created.  But, only update statistics during normal
! 	 * index definitions, not for indices on system catalogs created
! 	 * during bootstrap processing.  We must close the relations before
! 	 * updating statistics to guarantee that the relcache entries are
! 	 * flushed when we increment the command counter in UpdateStats(). But
! 	 * we do not release any locks on the relations; those will be held
! 	 * until end of transaction.
! 	 */
! 	if (IsNormalProcessingMode())
! 	{
! 		Oid			hrelid = RelationGetRelid(heap);
! 		Oid			irelid = RelationGetRelid(index);
! 
! 		heap_close(heap, NoLock);
! 		index_close(index);
! 		UpdateStats(hrelid, reltuples);
! 		UpdateStats(irelid, buildstate.indtuples);
! 	}
  
  	PG_RETURN_VOID();
  }
--- 142,149 ----
  
  	/* okay, all heap tuples are indexed */
  
! 	/* update stats for new index; see notes in update_index_stats */
! 	update_index_stats(heap, index, reltuples, buildstate.indtuples);
  
  	PG_RETURN_VOID();
  }
Index: src/backend/catalog/index.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/catalog/index.c,v
retrieving revision 1.215
diff -c -r1.215 index.c
*** src/backend/catalog/index.c	19 Sep 2003 19:57:42 -0000	1.215
--- src/backend/catalog/index.c	20 Sep 2003 07:04:42 -0000
***************
*** 1221,1226 ****
--- 1221,1254 ----
  	CommandCounterIncrement();
  }
  
+ /*
+  * This is called by the index AM just after a new index has been
+  * successfully built. Since we just counted the tuples in the heap, we
+  * update its stats in pg_class to guarantee that the planner takes
+  * advantage of the index we just created.  But, only update statistics
+  * during normal index definitions, not for indices on system catalogs
+  * created during bootstrap processing.  We must close the relations
+  * before updating statistics to guarantee that the relcache entries are
+  * flushed when we increment the command counter in UpdateStats(). But
+  * we do not release any locks on the relations; those will be held
+  * until end of transaction.
+  */
+ void
+ update_index_stats(Relation heap, Relation index,
+ 				   double reltuples, double indtuples)
+ {
+ 	if (IsNormalProcessingMode())
+ 	{
+ 		Oid			hrelid = RelationGetRelid(heap);
+ 		Oid			irelid = RelationGetRelid(index);
+ 
+ 		heap_close(heap, NoLock);
+ 		index_close(index);
+ 		UpdateStats(hrelid, reltuples);
+ 		UpdateStats(irelid, indtuples);
+ 	}
+ }
+ 
  
  /* ----------------
   *		UpdateStats
Index: src/backend/storage/lmgr/README
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/storage/lmgr/README,v
retrieving revision 1.13
diff -c -r1.13 README
*** src/backend/storage/lmgr/README	18 Feb 2003 03:33:50 -0000	1.13
--- src/backend/storage/lmgr/README	19 Sep 2003 20:39:34 -0000
***************
*** 73,79 ****
  
      tag.relId -
  	Uniquely identifies the relation that the lock corresponds to.
!     
      tag.dbId -
  	Uniquely identifies the database in which the relation lives.  If
  	this is a shared system relation (e.g. pg_database) the dbId must
--- 73,79 ----
  
      tag.relId -
  	Uniquely identifies the relation that the lock corresponds to.
! 
      tag.dbId -
  	Uniquely identifies the database in which the relation lives.  If
  	this is a shared system relation (e.g. pg_database) the dbId must
Index: src/include/access/genam.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/access/genam.h,v
retrieving revision 1.43
diff -c -r1.43 genam.h
*** src/include/access/genam.h	8 Aug 2003 21:42:32 -0000	1.43
--- src/include/access/genam.h	15 Sep 2003 21:34:17 -0000
***************
*** 94,100 ****
  extern RegProcedure index_cost_estimator(Relation indexRelation);
  extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
  				uint16 procnum);
! extern struct FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
  				  uint16 procnum);
  
  /*
--- 94,100 ----
  extern RegProcedure index_cost_estimator(Relation indexRelation);
  extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
  				uint16 procnum);
! extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
  				  uint16 procnum);
  
  /*
Index: src/include/access/hash.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/access/hash.h,v
retrieving revision 1.53
diff -c -r1.53 hash.h
*** src/include/access/hash.h	4 Sep 2003 22:06:27 -0000	1.53
--- src/include/access/hash.h	20 Sep 2003 07:28:40 -0000
***************
*** 166,172 ****
   * Maximum size of a hash index item (it's okay to have only one per page)
   */
  #define HashMaxItemSize(page) \
! 	(PageGetPageSize(page) - \
  	 sizeof(PageHeaderData) - \
  	 MAXALIGN(sizeof(HashPageOpaqueData)) - \
  	 sizeof(ItemIdData))
--- 166,172 ----
   * Maximum size of a hash index item (it's okay to have only one per page)
   */
  #define HashMaxItemSize(page) \
! 	(PageGetPageSize((Page) page) - \
  	 sizeof(PageHeaderData) - \
  	 MAXALIGN(sizeof(HashPageOpaqueData)) - \
  	 sizeof(ItemIdData))
***************
*** 208,215 ****
  #define HASH_WRITE		BUFFER_LOCK_EXCLUSIVE
  #define HASH_NOLOCK		(-1)
  
! #define HASH_SHARE		ShareLock
! #define HASH_EXCLUSIVE	ExclusiveLock
  
  /*
   *	Strategy number. There's only one valid strategy for hashing: equality.
--- 208,216 ----
  #define HASH_WRITE		BUFFER_LOCK_EXCLUSIVE
  #define HASH_NOLOCK		(-1)
  
! #define HASH_SHARE			AccessShareLock
! #define HASH_UNIQUE_INSERT	ShareUpdateExclusiveLock
! #define HASH_EXCLUSIVE		AccessExclusiveLock
  
  /*
   *	Strategy number. There's only one valid strategy for hashing: equality.
***************
*** 263,269 ****
  /* private routines */
  
  /* hashinsert.c */
! extern InsertIndexResult _hash_doinsert(Relation rel, HashItem hitem);
  
  /* hashovfl.c */
  extern Buffer _hash_addovflpage(Relation rel, Buffer metabuf, Buffer buf);
--- 264,271 ----
  /* private routines */
  
  /* hashinsert.c */
! extern InsertIndexResult _hash_doinsert(Relation rel, HashItem hitem,
! 										bool isUnique, Relation heapRel);
  
  /* hashovfl.c */
  extern Buffer _hash_addovflpage(Relation rel, Buffer metabuf, Buffer buf);
***************
*** 286,291 ****
--- 288,296 ----
  				   int to_access);
  extern void _hash_metapinit(Relation rel);
  extern void _hash_pageinit(Page page, Size size);
+ extern HashPageOpaque _hash_page_opaque_init(Page page,
+ 											 Bucket bucket,
+ 											 uint16 flag);
  extern void _hash_expandtable(Relation rel, Buffer metabuf);
  
  /* hashscan.c */
***************
*** 307,312 ****
--- 312,324 ----
  								   uint32 highmask, uint32 lowmask);
  extern uint32 _hash_log2(uint32 num);
  extern void _hash_checkpage(Relation rel, Page page, int flags);
+ extern bool _hash_need_split(HashMetaPage metap);
+ extern ScanKey _hash_mkscankey(Relation rel, IndexTuple itup);
+ extern void _hash_freeskey(ScanKey skey);
+ extern void _hash_readnext(Relation rel, Buffer *bufp,
+ 						   Page *pagep, HashPageOpaque *opaquep);
+ extern void _hash_readprev(Relation rel, Buffer *bufp,
+ 						   Page *pagep, HashPageOpaque *opaquep);
  
  /* hash.c */
  extern void hash_redo(XLogRecPtr lsn, XLogRecord *record);
Index: src/include/catalog/catversion.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/catalog/catversion.h,v
retrieving revision 1.208
diff -c -r1.208 catversion.h
*** src/include/catalog/catversion.h	17 Aug 2003 23:43:26 -0000	1.208
--- src/include/catalog/catversion.h	15 Sep 2003 17:53:03 -0000
***************
*** 53,58 ****
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	200308172
  
  #endif
--- 53,58 ----
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	200309151
  
  #endif
Index: src/include/catalog/index.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/catalog/index.h,v
retrieving revision 1.52
diff -c -r1.52 index.h
*** src/include/catalog/index.h	4 Aug 2003 02:40:10 -0000	1.52
--- src/include/catalog/index.h	5 Sep 2003 04:36:31 -0000
***************
*** 50,55 ****
--- 50,57 ----
  			   Datum *datum,
  			   char *nullv);
  
+ extern void update_index_stats(Relation heap, Relation index,
+ 							   double reltuples, double indtuples);
  extern void UpdateStats(Oid relid, double reltuples);
  extern bool IndexesAreActive(Relation heaprel);
  extern void setRelhasindex(Oid relid, bool hasindex,
Index: src/include/catalog/pg_am.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/catalog/pg_am.h,v
retrieving revision 1.28
diff -c -r1.28 pg_am.h
*** src/include/catalog/pg_am.h	4 Aug 2003 02:40:10 -0000	1.28
--- src/include/catalog/pg_am.h	12 Sep 2003 19:41:27 -0000
***************
*** 38,46 ****
  {
  	NameData	amname;			/* access method name */
  	int4		amowner;		/* usesysid of creator */
! 	int2		amstrategies;	/* total NUMBER of strategies (operators)
  								 * by which we can traverse/search this AM */
! 	int2		amsupport;		/* total NUMBER of support functions that
  								 * this AM uses */
  	int2		amorderstrategy;/* if this AM has a sort order, the
  								 * strategy number of the sort operator.
--- 38,46 ----
  {
  	NameData	amname;			/* access method name */
  	int4		amowner;		/* usesysid of creator */
! 	int2		amstrategies;	/* total number of strategies (operators)
  								 * by which we can traverse/search this AM */
! 	int2		amsupport;		/* total number of support functions that
  								 * this AM uses */
  	int2		amorderstrategy;/* if this AM has a sort order, the
  								 * strategy number of the sort operator.
***************
*** 105,111 ****
  DATA(insert OID = 403 (  btree	PGUID	5 1 1 t t t t btgettuple btinsert btbeginscan btrescan btendscan btmarkpos btrestrpos btbuild btbulkdelete btvacuumcleanup btcostestimate ));
  DESCR("b-tree index access method");
  #define BTREE_AM_OID 403
! DATA(insert OID = 405 (  hash	PGUID	1 1 0 f f f t hashgettuple hashinsert hashbeginscan hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbulkdelete - hashcostestimate ));
  DESCR("hash index access method");
  #define HASH_AM_OID 405
  DATA(insert OID = 783 (  gist	PGUID 100 7 0 f t f f gistgettuple gistinsert gistbeginscan gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbulkdelete - gistcostestimate ));
--- 105,111 ----
  DATA(insert OID = 403 (  btree	PGUID	5 1 1 t t t t btgettuple btinsert btbeginscan btrescan btendscan btmarkpos btrestrpos btbuild btbulkdelete btvacuumcleanup btcostestimate ));
  DESCR("b-tree index access method");
  #define BTREE_AM_OID 403
! DATA(insert OID = 405 (  hash	PGUID	1 1 0 t f f t hashgettuple hashinsert hashbeginscan hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbulkdelete - hashcostestimate ));
  DESCR("hash index access method");
  #define HASH_AM_OID 405
  DATA(insert OID = 783 (  gist	PGUID 100 7 0 f t f f gistgettuple gistinsert gistbeginscan gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbulkdelete - gistcostestimate ));
Index: src/include/storage/buf.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/storage/buf.h,v
retrieving revision 1.15
diff -c -r1.15 buf.h
*** src/include/storage/buf.h	4 Aug 2003 02:40:14 -0000	1.15
--- src/include/storage/buf.h	15 Sep 2003 18:33:30 -0000
***************
*** 32,38 ****
  
  /*
   * BufferIsLocal
!  *		True iff the buffer is local (not visible to other servers).
   */
  #define BufferIsLocal(buffer)	((buffer) < 0)
  
--- 32,38 ----
  
  /*
   * BufferIsLocal
!  *		True iff the buffer is local (not visible to other backends).
   */
  #define BufferIsLocal(buffer)	((buffer) < 0)
  
Index: src/test/regress/expected/create_index.out
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/test/regress/expected/create_index.out,v
retrieving revision 1.11
diff -c -r1.11 create_index.out
*** src/test/regress/expected/create_index.out	21 Jul 2003 20:29:40 -0000	1.11
--- src/test/regress/expected/create_index.out	15 Sep 2003 17:51:18 -0000
***************
*** 67,73 ****
  CREATE INDEX hash_name_index ON hash_name_heap USING hash (random name_ops);
  CREATE INDEX hash_txt_index ON hash_txt_heap USING hash (random text_ops);
  CREATE INDEX hash_f8_index ON hash_f8_heap USING hash (random float8_ops);
- -- CREATE INDEX hash_ovfl_index ON hash_ovfl_heap USING hash (x int4_ops);
  --
  -- Test functional index
  --
--- 67,72 ----
Index: src/test/regress/expected/create_table.out
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/test/regress/expected/create_table.out,v
retrieving revision 1.11
diff -c -r1.11 create_table.out
*** src/test/regress/expected/create_table.out	20 Jul 2003 21:56:35 -0000	1.11
--- src/test/regress/expected/create_table.out	12 Sep 2003 21:08:51 -0000
***************
*** 163,175 ****
  	seqno		int4,
  	random 		float8
  );
- -- don't include the hash_ovfl_heap stuff in the distribution
- -- the data set is too large for what it's worth
- -- 
- -- CREATE TABLE hash_ovfl_heap (
- --	x			int4,
- --	y			int4
- -- );
  CREATE TABLE bt_i4_heap (
  	seqno 		int4,
  	random 		int4
--- 163,168 ----
Index: src/test/regress/expected/hash_index.out
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/test/regress/expected/hash_index.out,v
retrieving revision 1.5
diff -c -r1.5 hash_index.out
*** src/test/regress/expected/hash_index.out	9 Jan 2000 03:48:37 -0000	1.5
--- src/test/regress/expected/hash_index.out	16 Sep 2003 05:25:00 -0000
***************
*** 83,100 ****
  (0 rows)
  
  --
- -- hash index
- -- grep '^90[^0-9]' hashovfl.data
- --
- -- SELECT count(*) AS i988 FROM hash_ovfl_heap
- --    WHERE x = 90;
- --
- -- hash index
- -- grep '^1000[^0-9]' hashovfl.data
- --
- -- SELECT count(*) AS i0 FROM hash_ovfl_heap
- --    WHERE x = 1000;
- --
  -- HASH
  --
  UPDATE hash_i4_heap
--- 83,88 ----
***************
*** 187,198 ****
    20000
  (1 row)
  
! -- UPDATE hash_ovfl_heap
! --    SET x = 1000
! --   WHERE x = 90;
! -- this vacuums the index as well
! -- VACUUM hash_ovfl_heap;
! -- SELECT count(*) AS i0 FROM hash_ovfl_heap
! --   WHERE x = 90;
! -- SELECT count(*) AS i988 FROM hash_ovfl_heap
! --  WHERE x = 1000;
--- 175,241 ----
    20000
  (1 row)
  
! --
! -- unique hash indexes
! --
! CREATE TABLE hash_uniq_test (
!     id     SERIAL
! );
! NOTICE:  CREATE TABLE will create implicit sequence "hash_uniq_test_id_seq" for SERIAL column "hash_uniq_test.id"
! CREATE UNIQUE INDEX hash_uniq_test_idx ON hash_uniq_test USING hash (id);
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! SELECT setval('hash_uniq_test_id_seq', 1);
!  setval 
! --------
!       1
! (1 row)
! 
! -- these should fail
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! -- drop the index, violate the constraint, and check that the violation
! -- is successfully detected when the index is recreated
! DROP INDEX hash_uniq_test_idx;
! INSERT INTO hash_uniq_test (id) VALUES (2);
! INSERT INTO hash_uniq_test (id) VALUES (3);
! -- should fail
! CREATE UNIQUE INDEX hash_uniq_test_idx ON hash_uniq_test USING hash (id);
! ERROR:  duplicate key violates UNIQUE constraint "hash_uniq_test_idx"
! DROP TABLE hash_uniq_test;
Index: src/test/regress/sql/create_index.sql
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/test/regress/sql/create_index.sql,v
retrieving revision 1.11
diff -c -r1.11 create_index.sql
*** src/test/regress/sql/create_index.sql	29 May 2003 01:09:13 -0000	1.11
--- src/test/regress/sql/create_index.sql	12 Sep 2003 21:06:30 -0000
***************
*** 88,96 ****
  
  CREATE INDEX hash_f8_index ON hash_f8_heap USING hash (random float8_ops);
  
- -- CREATE INDEX hash_ovfl_index ON hash_ovfl_heap USING hash (x int4_ops);
- 
- 
  --
  -- Test functional index
  --
--- 88,93 ----
Index: src/test/regress/sql/create_table.sql
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/test/regress/sql/create_table.sql,v
retrieving revision 1.7
diff -c -r1.7 create_table.sql
*** src/test/regress/sql/create_table.sql	21 May 2001 16:54:46 -0000	1.7
--- src/test/regress/sql/create_table.sql	12 Sep 2003 21:05:23 -0000
***************
*** 192,205 ****
  	random 		float8
  );
  
- -- don't include the hash_ovfl_heap stuff in the distribution
- -- the data set is too large for what it's worth
- -- 
- -- CREATE TABLE hash_ovfl_heap (
- --	x			int4,
- --	y			int4
- -- );
- 
  CREATE TABLE bt_i4_heap (
  	seqno 		int4,
  	random 		int4
--- 192,197 ----
Index: src/test/regress/sql/drop.sql
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/test/regress/sql/drop.sql,v
retrieving revision 1.6
diff -c -r1.6 drop.sql
*** src/test/regress/sql/drop.sql	22 Aug 2002 00:01:51 -0000	1.6
--- src/test/regress/sql/drop.sql	12 Sep 2003 21:05:37 -0000
***************
*** 130,137 ****
  
  DROP INDEX hash_f8_index;
  
- -- DROP INDEX hash_ovfl_index;
- 
  DROP INDEX bt_i4_index;
  
  DROP INDEX bt_name_index;
--- 130,135 ----
***************
*** 218,225 ****
  
  DROP TABLE  hash_f8_heap;
  
- -- DROP TABLE  hash_ovfl_heap;
- 
  DROP TABLE  bt_i4_heap;
  
  DROP TABLE  bt_name_heap;
--- 216,221 ----
Index: src/test/regress/sql/hash_index.sql
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/test/regress/sql/hash_index.sql,v
retrieving revision 1.5
diff -c -r1.5 hash_index.sql
*** src/test/regress/sql/hash_index.sql	6 Jan 2000 06:41:55 -0000	1.5
--- src/test/regress/sql/hash_index.sql	16 Sep 2003 05:23:40 -0000
***************
*** 56,75 ****
     WHERE hash_f8_heap.random = '88888888'::float8;
  
  --
- -- hash index
- -- grep '^90[^0-9]' hashovfl.data
- --
- -- SELECT count(*) AS i988 FROM hash_ovfl_heap
- --    WHERE x = 90;
- 
- --
- -- hash index
- -- grep '^1000[^0-9]' hashovfl.data
- --
- -- SELECT count(*) AS i0 FROM hash_ovfl_heap
- --    WHERE x = 1000;
- 
- --
  -- HASH
  --
  UPDATE hash_i4_heap
--- 56,61 ----
***************
*** 139,154 ****
     FROM hash_f8_heap h
     WHERE h.random = '488912369'::float8;
  
! -- UPDATE hash_ovfl_heap
! --    SET x = 1000
! --   WHERE x = 90;
  
! -- this vacuums the index as well
! -- VACUUM hash_ovfl_heap;
  
! -- SELECT count(*) AS i0 FROM hash_ovfl_heap
! --   WHERE x = 90;
  
! -- SELECT count(*) AS i988 FROM hash_ovfl_heap
! --  WHERE x = 1000;
  
--- 125,183 ----
     FROM hash_f8_heap h
     WHERE h.random = '488912369'::float8;
  
! --
! -- unique hash indexes
! --
! 
! CREATE TABLE hash_uniq_test (
!     id     SERIAL
! );
! 
! CREATE UNIQUE INDEX hash_uniq_test_idx ON hash_uniq_test USING hash (id);
! 
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! 
! SELECT setval('hash_uniq_test_id_seq', 1);
! 
! -- these should fail
! 
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! INSERT INTO hash_uniq_test DEFAULT VALUES;
! 
! -- drop the index, violate the constraint, and check that the violation
! -- is successfully detected when the index is recreated
! 
! DROP INDEX hash_uniq_test_idx;
  
! INSERT INTO hash_uniq_test (id) VALUES (2);
! INSERT INTO hash_uniq_test (id) VALUES (3);
  
! -- should fail
  
! CREATE UNIQUE INDEX hash_uniq_test_idx ON hash_uniq_test USING hash (id);
  
+ DROP TABLE hash_uniq_test;
\ No newline at end of file
