diff -cpr pgsql-orig/contrib/pgstattuple/README.pgstattuple pgsql/contrib/pgstattuple/README.pgstattuple
*** pgsql-orig/contrib/pgstattuple/README.pgstattuple	Tue Feb 28 01:09:49 2006
--- pgsql/contrib/pgstattuple/README.pgstattuple	Wed Jun 28 15:08:07 2006
*************** pgstattuple README			2002/08/29 Tatsuo I
*** 2,9 ****
  
  1. What is pgstattuple?
  
!    pgstattuple returns the table length, percentage of the "dead"
!    tuples of a table and other info. This may help users to determine
     whether vacuum is necessary or not. Here is an example session:
  
  test=# \x
--- 2,9 ----
  
  1. What is pgstattuple?
  
!    pgstattuple returns the relation length, percentage of the "dead"
!    tuples of a relation and other info. This may help users to determine
     whether vacuum is necessary or not. Here is an example session:
  
  test=# \x
*************** free_percent       | 1.95
*** 23,29 ****
  
  Here are explanations for each column:
  
! table_len		-- physical table length in bytes
  tuple_count		-- number of live tuples
  tuple_len		-- total tuples length in bytes
  tuple_percent		-- live tuples in %
--- 23,29 ----
  
  Here are explanations for each column:
  
! table_len		-- physical relation length in bytes
  tuple_count		-- number of live tuples
  tuple_len		-- total tuples length in bytes
  tuple_percent		-- live tuples in %
*************** free_percent		-- free space in %
*** 40,46 ****
  
  3. Using pgstattuple
  
!    pgstattuple may be called as a table function and is
     defined as follows:
  
     CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS pgstattuple_type
--- 40,46 ----
  
  3. Using pgstattuple
  
!    pgstattuple may be called as a relation function and is
     defined as follows:
  
     CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS pgstattuple_type
*************** free_percent		-- free space in %
*** 51,64 ****
       AS 'MODULE_PATHNAME', 'pgstattuplebyid'
       LANGUAGE C STRICT;
  
!    The argument is the table name (optionally it may be qualified)
!    or the OID of the table.  Note that pgstattuple only returns
     one row.
  
  4. Notes
  
!    pgstattuple acquires only a read lock on the table. So concurrent
     update may affect the result.
  
     pgstattuple judges a tuple is "dead" if HeapTupleSatisfiesNow()
     returns false.
--- 51,70 ----
       AS 'MODULE_PATHNAME', 'pgstattuplebyid'
       LANGUAGE C STRICT;
  
!    The argument is the relation name (optionally it may be qualified)
!    or the OID of the relation.  Note that pgstattuple only returns
     one row.
  
  4. Notes
  
!    pgstattuple acquires only a read lock on the relation. So concurrent
     update may affect the result.
  
     pgstattuple judges a tuple is "dead" if HeapTupleSatisfiesNow()
     returns false.
+ 
+ 5. History
+ 
+    2006/06/28
+ 
+ 	Extended to work against indexes.
diff -cpr pgsql-orig/contrib/pgstattuple/README.pgstattuple.euc_jp pgsql/contrib/pgstattuple/README.pgstattuple.euc_jp
*** pgsql-orig/contrib/pgstattuple/README.pgstattuple.euc_jp	Tue Feb 28 01:09:49 2006
--- pgsql/contrib/pgstattuple/README.pgstattuple.euc_jp	Wed Jun 28 15:08:08 2006
*************** pgstattuple README		2002/08/22 石井達夫
*** 4,11 ****
  
  1. pgstattupleとは
  
!    pgstattupleは，UPDATEやDELETEで作られたテーブルのゴミ領域の大きさを，
!    テーブル自体の物理的な大きさに対するパーセンテージで返却します．つ
     まり，返却値が大きければ，それだけゴミも多いので，vacuumをかける必
     要があるという判断の助けになるわけです．これ以外にもいろいろな情報
     が返ります．
--- 4,11 ----
  
  1. pgstattupleとは
  
!    pgstattupleは，UPDATEやDELETEで作られたリレーションのゴミ領域の大きさを，
!    リレーション自体の物理的な大きさに対するパーセンテージで返却します．つ
     まり，返却値が大きければ，それだけゴミも多いので，vacuumをかける必
     要があるという判断の助けになるわけです．これ以外にもいろいろな情報
     が返ります．
*************** free_percent       | 1.95
*** 26,32 ****
  
  各項目の説明です．
  
! table_len		-- テーブルの物理的な大きさ(バイト)
  tuple_count		-- タプル数
  tuple_len		-- タプル長の合計(バイト)
  tuple_percent		-- タプルの割合．table_lenに対するtuple_lenの比率．
--- 26,32 ----
  
  各項目の説明です．
  
! table_len		-- リレーションの物理的な大きさ(バイト)
  tuple_count		-- タプル数
  tuple_len		-- タプル長の合計(バイト)
  tuple_percent		-- タプルの割合．table_lenに対するtuple_lenの比率．
*************** free_percent		-- 再利用可能な領域．table
*** 56,67 ****
       AS 'MODULE_PATHNAME', 'pgstattuple'
       LANGUAGE C STRICT;
  
!    第一引数: テーブル名
  
     関数の戻りはpgstattuple_type型です．
  
!    pgstattupleはテーブルにAccessShareLockしかかけないので，
!    pgstattuple を実行中に該当テーブルに更新や削除が発生すると，正しく
     ない結果を返す可能性があります．
  
     pgstattupleがタプルを「ゴミ」と判断する基準は，
--- 56,67 ----
       AS 'MODULE_PATHNAME', 'pgstattuple'
       LANGUAGE C STRICT;
  
!    第一引数: リレーション名
  
     関数の戻りはpgstattuple_type型です．
  
!    pgstattupleはリレーションにAccessShareLockしかかけないので，
!    pgstattuple を実行中に該当リレーションに更新や削除が発生すると，正しく
     ない結果を返す可能性があります．
  
     pgstattupleがタプルを「ゴミ」と判断する基準は，
*************** free_percent		-- 再利用可能な領域．table
*** 74,79 ****
--- 74,83 ----
     も責任を負いません．
  
  5. 改訂履歴
+ 
+    2006/06/28
+ 
+ 	インデックスに対しても動作するように拡張。
  
     2002/09/04
  
diff -cpr pgsql-orig/contrib/pgstattuple/pgstattuple.c pgsql/contrib/pgstattuple/pgstattuple.c
*** pgsql-orig/contrib/pgstattuple/pgstattuple.c	Fri Jun  2 07:36:35 2006
--- pgsql/contrib/pgstattuple/pgstattuple.c	Tue Jun 27 13:17:13 2006
***************
*** 27,32 ****
--- 27,35 ----
  #include "fmgr.h"
  #include "funcapi.h"
  #include "access/heapam.h"
+ #include "access/nbtree.h"
+ #include "access/gist_private.h"
+ #include "access/hash.h"
  #include "access/transam.h"
  #include "catalog/namespace.h"
  #include "utils/builtins.h"
*************** PG_FUNCTION_INFO_V1(pgstattuplebyid);
*** 40,47 ****
  extern Datum pgstattuple(PG_FUNCTION_ARGS);
  extern Datum pgstattuplebyid(PG_FUNCTION_ARGS);
  
! static Datum pgstattuple_real(Relation rel, FunctionCallInfo fcinfo);
  
  
  /* ----------
   * pgstattuple:
--- 43,166 ----
  extern Datum pgstattuple(PG_FUNCTION_ARGS);
  extern Datum pgstattuplebyid(PG_FUNCTION_ARGS);
  
! /*
!  * struct pgstattuple_type
!  *
!  * tuple_percent, dead_tuple_percent and free_percent are computable,
!  * so not defined here.
!  */
! typedef struct pgstattuple_type
! {
! 	uint64	table_len;
! 	uint64	tuple_count;
! 	uint64	tuple_len;
! 	uint64	dead_tuple_count;
! 	uint64	dead_tuple_len;
! 	uint64	free_space;			/* free/reusable space in bytes */
! } pgstattuple_type;
! 
! /*
!  * struct pgstat_btree_type
!  */
! typedef struct pgstat_btree_type
! {
! 	pgstattuple_type	base;	/* inherits pgstattuple_type */
! 
! 	uint64	continuous;
! 	uint64	forward;
! 	uint64	backward;
! } pgstat_btree_type;
! 
! typedef void (*pgstat_page)(pgstattuple_type *, Relation, BlockNumber);
! 
! static Datum build_pgstattuple_type(pgstattuple_type *stat,
! 	FunctionCallInfo fcinfo);
! static Datum pgstat_relation(Relation rel, FunctionCallInfo fcinfo);
! static Datum pgstat_heap(Relation rel, FunctionCallInfo fcinfo);
! static Datum pgstat_btree(Relation rel, FunctionCallInfo fcinfo);
! static void pgstat_btree_page(pgstattuple_type *stat,
! 	Relation rel, BlockNumber blkno);
! static Datum pgstat_hash(Relation rel, FunctionCallInfo fcinfo);
! static void pgstat_hash_page(pgstattuple_type *stat,
! 	Relation rel, BlockNumber blkno);
! static Datum pgstat_gist(Relation rel, FunctionCallInfo fcinfo);
! static void pgstat_gist_page(pgstattuple_type *stat,
! 	Relation rel, BlockNumber blkno);
! static Datum pgstat_index(pgstattuple_type *stat,
! 	Relation rel, BlockNumber start,
! 	pgstat_page pagefn, FunctionCallInfo fcinfo);
! static void pgstat_index_page(pgstattuple_type *stat, Page page,
! 	OffsetNumber minoff, OffsetNumber maxoff);
! 
! /*
!  * build_pgstattuple_type -- build a pgstattuple_type tuple
!  */
! static Datum
! build_pgstattuple_type(pgstattuple_type *stat, FunctionCallInfo fcinfo)
! {
! #define NCOLUMNS	9
! #define NCHARS		32
! 
! 	HeapTuple	tuple;
! 	char	   *values[NCOLUMNS];
! 	char		values_buf[NCOLUMNS][NCHARS];
! 	int			i;
! 	double		tuple_percent;
! 	double		dead_tuple_percent;
! 	double		free_percent;		/* free/reusable space in % */
! 	TupleDesc	tupdesc;
! 	AttInMetadata *attinmeta;
! 
! 	/* Build a tuple descriptor for our result type */
! 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! 		elog(ERROR, "return type must be a row type");
! 
! 	/* make sure we have a persistent copy of the tupdesc */
! 	tupdesc = CreateTupleDescCopy(tupdesc);
! 
! 	/*
! 	 * Generate attribute metadata needed later to produce tuples from raw C
! 	 * strings
! 	 */
! 	attinmeta = TupleDescGetAttInMetadata(tupdesc);
! 
! 	if (stat->table_len == 0)
! 	{
! 		tuple_percent = 0.0;
! 		dead_tuple_percent = 0.0;
! 		free_percent = 0.0;
! 	}
! 	else
! 	{
! 		tuple_percent = 100.0 * stat->tuple_len / stat->table_len;
! 		dead_tuple_percent = 100.0 * stat->dead_tuple_len / stat->table_len;
! 		free_percent = 100.0 * stat->free_space / stat->table_len;
! 	}
! 
! 	/*
! 	 * Prepare a values array for constructing the tuple. This should be an
! 	 * array of C strings which will be processed later by the appropriate
! 	 * "in" functions.
! 	 */
! 	for (i = 0; i < NCOLUMNS; i++)
! 		values[i] = values_buf[i];
! 	i = 0;
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, stat->table_len);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, stat->tuple_count);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, stat->tuple_len);
! 	snprintf(values[i++], NCHARS, "%.2f", tuple_percent);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, stat->dead_tuple_count);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, stat->dead_tuple_len);
! 	snprintf(values[i++], NCHARS, "%.2f", dead_tuple_percent);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, stat->free_space);
! 	snprintf(values[i++], NCHARS, "%.2f", free_percent);
  
+ 	/* build a tuple */
+ 	tuple = BuildTupleFromCStrings(attinmeta, values);
+ 
+ 	/* make the tuple into a datum */
+ 	return HeapTupleGetDatum(tuple);
+ }
  
  /* ----------
   * pgstattuple:
*************** static Datum pgstattuple_real(Relation r
*** 53,76 ****
   * ----------
   */
  
- #define NCOLUMNS 9
- #define NCHARS 32
- 
  Datum
  pgstattuple(PG_FUNCTION_ARGS)
  {
  	text	   *relname = PG_GETARG_TEXT_P(0);
  	RangeVar   *relrv;
  	Relation	rel;
- 	Datum		result;
  
  	/* open relation */
  	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
! 	rel = heap_openrv(relrv, AccessShareLock);
! 
! 	result = pgstattuple_real(rel, fcinfo);
  
! 	PG_RETURN_DATUM(result);
  }
  
  Datum
--- 172,189 ----
   * ----------
   */
  
  Datum
  pgstattuple(PG_FUNCTION_ARGS)
  {
  	text	   *relname = PG_GETARG_TEXT_P(0);
  	RangeVar   *relrv;
  	Relation	rel;
  
  	/* open relation */
  	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
! 	rel = relation_openrv(relrv, AccessShareLock);
  
! 	PG_RETURN_DATUM(pgstat_relation(rel, fcinfo));
  }
  
  Datum
*************** pgstattuplebyid(PG_FUNCTION_ARGS)
*** 78,134 ****
  {
  	Oid			relid = PG_GETARG_OID(0);
  	Relation	rel;
- 	Datum		result;
  
  	/* open relation */
! 	rel = heap_open(relid, AccessShareLock);
! 
! 	result = pgstattuple_real(rel, fcinfo);
  
! 	PG_RETURN_DATUM(result);
  }
  
  /*
!  * pgstattuple_real
!  *
!  * The real work occurs here
   */
  static Datum
! pgstattuple_real(Relation rel, FunctionCallInfo fcinfo)
  {
! 	HeapScanDesc scan;
! 	HeapTuple	tuple;
! 	BlockNumber nblocks;
! 	BlockNumber block = 0;		/* next block to count free space in */
! 	BlockNumber tupblock;
! 	Buffer		buffer;
! 	uint64		table_len;
! 	uint64		tuple_len = 0;
! 	uint64		dead_tuple_len = 0;
! 	uint64		tuple_count = 0;
! 	uint64		dead_tuple_count = 0;
! 	double		tuple_percent;
! 	double		dead_tuple_percent;
! 	uint64		free_space = 0; /* free/reusable space in bytes */
! 	double		free_percent;	/* free/reusable space in % */
! 	TupleDesc	tupdesc;
! 	AttInMetadata *attinmeta;
! 	char	  **values;
! 	int			i;
! 	Datum		result;
  
! 	/* Build a tuple descriptor for our result type */
! 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! 		elog(ERROR, "return type must be a row type");
  
! 	/* make sure we have a persistent copy of the tupdesc */
! 	tupdesc = CreateTupleDescCopy(tupdesc);
  
! 	/*
! 	 * Generate attribute metadata needed later to produce tuples from raw C
! 	 * strings
! 	 */
! 	attinmeta = TupleDescGetAttInMetadata(tupdesc);
  
  	scan = heap_beginscan(rel, SnapshotAny, 0, NULL);
  
--- 191,266 ----
  {
  	Oid			relid = PG_GETARG_OID(0);
  	Relation	rel;
  
  	/* open relation */
! 	rel = relation_open(relid, AccessShareLock);
  
! 	PG_RETURN_DATUM(pgstat_relation(rel, fcinfo));
  }
  
  /*
!  * pgstat_relation
   */
  static Datum
! pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
  {
! 	const char *err;
  
! 	switch(rel->rd_rel->relkind)
! 	{
! 	case RELKIND_RELATION:
! 	case RELKIND_TOASTVALUE:
! 	case RELKIND_UNCATALOGED:
! 	case RELKIND_SEQUENCE:
! 		return pgstat_heap(rel, fcinfo);
! 	case RELKIND_INDEX:
! 		switch(rel->rd_rel->relam)
! 		{
! 		case BTREE_AM_OID:
! 			return pgstat_btree(rel, fcinfo);
! 		case HASH_AM_OID:
! 			return pgstat_hash(rel, fcinfo);
! 		case GIST_AM_OID:
! 			return pgstat_gist(rel, fcinfo);
! 		case GIN_AM_OID:
! 			err = "gin index";
! 			break;
! 		default:
! 			err = "unknown index";
! 			break;
! 		}
! 		break;
! 	case RELKIND_VIEW:
! 		err = "view";
! 		break;
! 	case RELKIND_COMPOSITE_TYPE:
! 		err = "composite type";
! 		break;
! 	default:
! 		err = "unknown";
! 		break;
! 	}
  
! 	ereport(ERROR,
! 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 			 errmsg("\"%s\" (%s) is not supported",
! 				RelationGetRelationName(rel), err)));
! 	return 0;	/* should not happen */
! }
  
! /*
!  * pgstat_heap -- returns live/dead tuples info in a heap
!  */
! static Datum
! pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
! {
! 	HeapScanDesc	scan;
! 	HeapTuple		tuple;
! 	BlockNumber		nblocks;
! 	BlockNumber		block = 0;	/* next block to count free space in */
! 	BlockNumber		tupblock;
! 	Buffer			buffer;
! 	pgstattuple_type	stat = { 0 };
  
  	scan = heap_beginscan(rel, SnapshotAny, 0, NULL);
  
*************** pgstattuple_real(Relation rel, FunctionC
*** 142,154 ****
  
  		if (HeapTupleSatisfiesNow(tuple->t_data, scan->rs_cbuf))
  		{
! 			tuple_len += tuple->t_len;
! 			tuple_count++;
  		}
  		else
  		{
! 			dead_tuple_len += tuple->t_len;
! 			dead_tuple_count++;
  		}
  
  		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
--- 274,286 ----
  
  		if (HeapTupleSatisfiesNow(tuple->t_data, scan->rs_cbuf))
  		{
! 			stat.tuple_len += tuple->t_len;
! 			stat.tuple_count++;
  		}
  		else
  		{
! 			stat.dead_tuple_len += tuple->t_len;
! 			stat.dead_tuple_count++;
  		}
  
  		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
*************** pgstattuple_real(Relation rel, FunctionC
*** 165,171 ****
  		{
  			buffer = ReadBuffer(rel, block);
  			LockBuffer(buffer, BUFFER_LOCK_SHARE);
! 			free_space += PageGetFreeSpace((Page) BufferGetPage(buffer));
  			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
  			ReleaseBuffer(buffer);
  			block++;
--- 297,303 ----
  		{
  			buffer = ReadBuffer(rel, block);
  			LockBuffer(buffer, BUFFER_LOCK_SHARE);
! 			stat.free_space += PageGetFreeSpace((Page) BufferGetPage(buffer));
  			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
  			ReleaseBuffer(buffer);
  			block++;
*************** pgstattuple_real(Relation rel, FunctionC
*** 176,232 ****
  	while (block < nblocks)
  	{
  		buffer = ReadBuffer(rel, block);
! 		free_space += PageGetFreeSpace((Page) BufferGetPage(buffer));
  		ReleaseBuffer(buffer);
  		block++;
  	}
  
! 	heap_close(rel, AccessShareLock);
  
! 	table_len = (uint64) nblocks *BLCKSZ;
  
! 	if (nblocks == 0)
  	{
! 		tuple_percent = 0.0;
! 		dead_tuple_percent = 0.0;
! 		free_percent = 0.0;
  	}
  	else
  	{
! 		tuple_percent = (double) tuple_len *100.0 / table_len;
! 		dead_tuple_percent = (double) dead_tuple_len *100.0 / table_len;
! 		free_percent = (double) free_space *100.0 / table_len;
  	}
  
! 	/*
! 	 * Prepare a values array for constructing the tuple. This should be an
! 	 * array of C strings which will be processed later by the appropriate
! 	 * "in" functions.
! 	 */
! 	values = (char **) palloc(NCOLUMNS * sizeof(char *));
! 	for (i = 0; i < NCOLUMNS; i++)
! 		values[i] = (char *) palloc(NCHARS * sizeof(char));
! 	i = 0;
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, table_len);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, tuple_count);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, tuple_len);
! 	snprintf(values[i++], NCHARS, "%.2f", tuple_percent);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, dead_tuple_count);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, dead_tuple_len);
! 	snprintf(values[i++], NCHARS, "%.2f", dead_tuple_percent);
! 	snprintf(values[i++], NCHARS, INT64_FORMAT, free_space);
! 	snprintf(values[i++], NCHARS, "%.2f", free_percent);
  
! 	/* build a tuple */
! 	tuple = BuildTupleFromCStrings(attinmeta, values);
  
! 	/* make the tuple into a datum */
! 	result = HeapTupleGetDatum(tuple);
  
! 	/* Clean up */
! 	for (i = 0; i < NCOLUMNS; i++)
! 		pfree(values[i]);
! 	pfree(values);
  
! 	return (result);
  }
--- 308,547 ----
  	while (block < nblocks)
  	{
  		buffer = ReadBuffer(rel, block);
! 		stat.free_space += PageGetFreeSpace((Page) BufferGetPage(buffer));
  		ReleaseBuffer(buffer);
  		block++;
  	}
  
! 	relation_close(rel, AccessShareLock);
! 
! 	stat.table_len = (uint64) nblocks * BLCKSZ;
! 
! 	return build_pgstattuple_type(&stat, fcinfo);
! }
! 
! /*
!  * pgstat_btree -- returns live/dead tuples info in a btree index
!  */
! static Datum
! pgstat_btree(Relation rel, FunctionCallInfo fcinfo)
! {
! 	pgstat_btree_type	stat = { { 0 } };
! 	Datum				datum;
! 
! 	datum = pgstat_index((pgstattuple_type *) &stat, rel,
! 		BTREE_METAPAGE + 1, pgstat_btree_page, fcinfo);
! 
! 	ereport(NOTICE,
! 		(errmsg("%.2f%% fragmented",
! 			100.0 * (stat.forward + stat.backward) /
! 			(stat.continuous + stat.forward + stat.backward)),
! 		errhint("continuous=%llu, forward=%llu, backward=%llu",
! 			stat.continuous, stat.forward, stat.backward)));
! 
! 	return datum;
! }
! 
! /*
!  * pgstat_btree_page
!  */
! static void
! pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
! {
! 	Buffer				buf;
! 	Page				page;
! 	pgstat_btree_type  *btstat = (pgstat_btree_type *)stat;
! 
! 	buf = ReadBuffer(rel, blkno);
! 	LockBuffer(buf, BT_READ);
! 	page = BufferGetPage(buf);
! 
! 	/* Page is valid, see what to do with it */
! 	if (PageIsNew(page))
! 	{
! 		/* fully empty page */
! 		stat->free_space += BLCKSZ;
! 	}
! 	else
! 	{
! 		BTPageOpaque	opaque;
! 		opaque = (BTPageOpaque) PageGetSpecialPointer(page);
! 		if (opaque->btpo_flags & (BTP_DELETED | BTP_HALF_DEAD))
! 		{
! 			/* recyclable page */
! 			stat->free_space += BLCKSZ;
! 		}
! 		else if (P_ISLEAF(opaque))
! 		{
! 			/* check fragmentation */
! 			if (P_RIGHTMOST(opaque))
! 				btstat->continuous++;
! 			else if (opaque->btpo_next < blkno)
! 				btstat->backward++;
! 			else if (opaque->btpo_next > blkno + 1)
! 				btstat->forward++;
! 			else
! 				btstat->continuous++;
! 
! 			pgstat_index_page(stat, page, P_FIRSTDATAKEY(opaque),
! 				PageGetMaxOffsetNumber(page));
! 		}
! 		else
! 		{
! 			/* root or node */
! 		}
! 	}
  
! 	_bt_relbuf(rel, buf);
! }
  
! /*
!  * pgstat_hash -- returns live/dead tuples info in a hash index
!  */
! static Datum
! pgstat_hash(Relation rel, FunctionCallInfo fcinfo)
! {
! 	pgstattuple_type	stat = { 0 };
! 	return pgstat_index(&stat, rel, HASH_METAPAGE + 1, pgstat_hash_page, fcinfo);
! }
! 
! /*
!  * pgstat_hash_page
!  */
! static void
! pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
! {
! 	Buffer			buf;
! 	Page			page;
! 
! 	_hash_getlock(rel, blkno, HASH_SHARE);
! 	buf = _hash_getbuf(rel, blkno, HASH_READ);
! 	page = BufferGetPage(buf);
! 
! 	if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
  	{
! 		HashPageOpaque	opaque;
! 		opaque = (HashPageOpaque) PageGetSpecialPointer(page);
! 		switch (opaque->hasho_flag)
! 		{
! 		case LH_UNUSED_PAGE:
! 			stat->free_space += BLCKSZ;
! 			break;
! 		case LH_BUCKET_PAGE:
! 		case LH_OVERFLOW_PAGE:
! 			pgstat_index_page(stat, page, FirstOffsetNumber,
! 				PageGetMaxOffsetNumber(page));
! 			break;
! 		case LH_BITMAP_PAGE:
! 		case LH_META_PAGE:
! 		default:
! 			break;
! 		}
  	}
  	else
  	{
! 		/* maybe corrupted */
  	}
  
! 	_hash_relbuf(rel, buf);
! 	_hash_droplock(rel, blkno, HASH_SHARE);
! }
  
! /*
!  * pgstat_gist -- returns live/dead tuples info in a gist index
!  */
! static Datum
! pgstat_gist(Relation rel, FunctionCallInfo fcinfo)
! {
! 	pgstattuple_type	stat = { 0 };
! 	return pgstat_index(&stat, rel, GIST_ROOT_BLKNO + 1, pgstat_gist_page, fcinfo);
! }
  
! /*
!  * pgstat_gist_page
!  */
! static void
! pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
! {
! 	Buffer			buf;
! 	Page			page;
  
! 	buf = ReadBuffer(rel, blkno);
! 	LockBuffer(buf, GIST_SHARE);
! 	gistcheckpage(rel, buf);
! 	page = BufferGetPage(buf);
  
! 	if (GistPageIsLeaf(page))
! 	{
! 		pgstat_index_page(stat, page, FirstOffsetNumber,
! 			PageGetMaxOffsetNumber(page));
! 	}
! 	else
! 	{
! 		/* root or node */
! 	}
! 
! 	UnlockReleaseBuffer(buf);
! }
! 
! /*
!  * pgstat_index -- returns live/dead tuples info in a generic index
!  */
! static Datum
! pgstat_index(pgstattuple_type *stat, Relation rel, BlockNumber start,
! 	pgstat_page pagefn, FunctionCallInfo fcinfo)
! {
! 	BlockNumber nblocks;
! 	BlockNumber blkno;
! 
! 	blkno = start;
! 	for (;;)
! 	{
! 		/* Get the current relation length */
! 		LockRelationForExtension(rel, ExclusiveLock);
! 		nblocks = RelationGetNumberOfBlocks(rel);
! 		UnlockRelationForExtension(rel, ExclusiveLock);
! 
! 		/* Quit if we've scanned the whole relation */
! 		if (blkno >= nblocks)
! 		{
! 			stat->table_len = (uint64) nblocks * BLCKSZ;
! 			break;
! 		}
! 
! 		for (; blkno < nblocks; blkno++)
! 			pagefn(stat, rel, blkno);
! 	}
! 
! 	relation_close(rel, AccessShareLock);
! 
! 	return build_pgstattuple_type(stat, fcinfo);
! }
! 
! /*
!  * pgstat_index_page -- for generic index page
!  */
! static void
! pgstat_index_page(pgstattuple_type *stat, Page page,
! 	OffsetNumber minoff, OffsetNumber maxoff)
! {
! 	OffsetNumber	i;
! 
! 	stat->free_space += PageGetFreeSpace(page);
! 
! 	for (i = minoff; i <= maxoff; i = OffsetNumberNext(i))
! 	{
! 		ItemId	itemid = PageGetItemId(page, i);
! 
! 		if (ItemIdDeleted(itemid))
! 		{
! 			stat->dead_tuple_count++;
! 			stat->dead_tuple_len += ItemIdGetLength(itemid);
! 		}
! 		else
! 		{
! 			stat->tuple_count++;
! 			stat->tuple_len += ItemIdGetLength(itemid);
! 		}
! 	}
  }
