diff -cpr pgsql-orig/contrib/pgstattuple/README.pgstattuple pgsql/contrib/pgstattuple/README.pgstattuple
*** pgsql-orig/contrib/pgstattuple/README.pgstattuple	Wed Jul 12 08:27:09 2006
--- pgsql/contrib/pgstattuple/README.pgstattuple	Mon Jul 24 15:36:21 2006
*************** dead_tuple_len     | 3157
*** 19,24 ****
--- 19,25 ----
  dead_tuple_percent | 0.69
  free_space         | 8932
  free_percent       | 1.95
+ fragmented_percent |
  
  
  Here are explanations for each column:
*************** dead_tuple_len		-- total dead tuples len
*** 31,36 ****
--- 32,38 ----
  dead_tuple_percent	-- dead tuples in %
  free_space		-- free space in bytes
  free_percent		-- free space in %
+ fragmented_percent	-- fragmented factor in % (only available for btree)
  
  2. Installing pgstattuple
  
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	Wed Jul 12 08:27:09 2006
--- pgsql/contrib/pgstattuple/README.pgstattuple.euc_jp	Mon Jul 24 15:36:36 2006
*************** dead_tuple_len     | 3157
*** 23,28 ****
--- 23,29 ----
  dead_tuple_percent | 0.69
  free_space         | 8932
  free_percent       | 1.95
+ fragmented_percent |
  
  各項目の説明です．
  
*************** dead_tuple_len		-- デッドタプル数
*** 34,39 ****
--- 35,41 ----
  dead_tuple_percent	-- デッドタプルの割合．table_lenに対するtuple_lenの比率．
  free_space		-- 再利用可能な領域(バイト)
  free_percent		-- 再利用可能な領域．table_lenに対するfree_spaceの比率．
+ fragmented_percent	-- 断片化率(btreeでのみ利用可)
  
  2. pgstattupleのインストール
  
diff -cpr pgsql-orig/contrib/pgstattuple/pgstattuple.c pgsql/contrib/pgstattuple/pgstattuple.c
*** pgsql-orig/contrib/pgstattuple/pgstattuple.c	Wed Jul 12 08:27:09 2006
--- pgsql/contrib/pgstattuple/pgstattuple.c	Mon Jul 24 14:02:50 2006
*************** typedef struct pgstattuple_type
*** 57,62 ****
--- 57,63 ----
  	uint64	dead_tuple_count;
  	uint64	dead_tuple_len;
  	uint64	free_space;			/* free/reusable space in bytes */
+ 	double	fragmented_percent;
  } pgstattuple_type;
  
  /*
*************** typedef struct pgstattuple_type
*** 65,74 ****
  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);
--- 66,73 ----
  typedef struct pgstat_btree_type
  {
  	pgstattuple_type	base;	/* inherits pgstattuple_type */
  	uint64	continuous;
! 	uint64	discontinuous;
  } pgstat_btree_type;
  
  typedef void (*pgstat_page)(pgstattuple_type *, Relation, BlockNumber);
*************** static void pgstat_hash_page(pgstattuple
*** 86,94 ****
  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);
  
--- 85,92 ----
  static Datum pgstat_gist(Relation rel, FunctionCallInfo fcinfo);
  static void pgstat_gist_page(pgstattuple_type *stat,
  	Relation rel, BlockNumber blkno);
! static void pgstat_index(pgstattuple_type *stat,
! 	Relation rel, BlockNumber start, pgstat_page pagefn);
  static void pgstat_index_page(pgstattuple_type *stat, Page page,
  	OffsetNumber minoff, OffsetNumber maxoff);
  
*************** static void pgstat_index_page(pgstattupl
*** 98,115 ****
  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)
--- 96,111 ----
  static Datum
  build_pgstattuple_type(pgstattuple_type *stat, FunctionCallInfo fcinfo)
  {
! #define NCOLUMNS	10
  
  	HeapTuple	tuple;
! 	char		nulls[NCOLUMNS];
! 	Datum		values[NCOLUMNS];
  	int			i;
  	double		tuple_percent;
  	double		dead_tuple_percent;
  	double		free_percent;		/* free/reusable space in % */
  	TupleDesc	tupdesc;
  
  	/* Build a tuple descriptor for our result type */
  	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
*************** build_pgstattuple_type(pgstattuple_type 
*** 118,129 ****
  	/* 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;
--- 114,119 ----
*************** build_pgstattuple_type(pgstattuple_type 
*** 137,162 ****
  		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);
--- 127,152 ----
  		free_percent = 100.0 * stat->free_space / stat->table_len;
  	}
  
! 	/* build a tuple */
! 	MemSet(values, 0, sizeof(values));
! 	MemSet(nulls, ' ', sizeof(nulls));
! 
  	i = 0;
! 	values[i++] = Int64GetDatum(stat->table_len);
! 	values[i++] = Int64GetDatum(stat->tuple_count);
! 	values[i++] = Int64GetDatum(stat->tuple_len);
! 	values[i++] = Float8GetDatum(tuple_percent);
! 	values[i++] = Int64GetDatum(stat->dead_tuple_count);
! 	values[i++] = Int64GetDatum(stat->dead_tuple_len);
! 	values[i++] = Float8GetDatum(dead_tuple_percent);
! 	values[i++] = Int64GetDatum(stat->free_space);
! 	values[i++] = Float8GetDatum(free_percent);
! 	if (stat->fragmented_percent >= 0.0)
! 		values[i++] = Float8GetDatum(stat->fragmented_percent);
! 	else
! 		nulls[i++] = 'n';
  
! 	tuple = heap_formtuple(tupdesc, values, nulls);
  
  	/* make the tuple into a datum */
  	return HeapTupleGetDatum(tuple);
*************** pgstat_heap(Relation rel, FunctionCallIn
*** 316,321 ****
--- 306,312 ----
  	relation_close(rel, AccessShareLock);
  
  	stat.table_len = (uint64) nblocks * BLCKSZ;
+ 	stat.fragmented_percent = -1;	/* not supported */
  
  	return build_pgstattuple_type(&stat, fcinfo);
  }
*************** static Datum
*** 327,345 ****
  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;
  }
  
  /*
--- 318,331 ----
  pgstat_btree(Relation rel, FunctionCallInfo fcinfo)
  {
  	pgstat_btree_type	stat = { { 0 } };
  
! 	pgstat_index((pgstattuple_type *) &stat, rel,
! 		BTREE_METAPAGE + 1, pgstat_btree_page);
  
! 	stat.base.fragmented_percent = 100.0 * stat.discontinuous /
! 		(stat.continuous + stat.discontinuous);
  
! 	return build_pgstattuple_type((pgstattuple_type *) &stat, fcinfo);
  }
  
  /*
*************** pgstat_btree_page(pgstattuple_type *stat
*** 374,387 ****
  		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));
--- 360,369 ----
  		else if (P_ISLEAF(opaque))
  		{
  			/* check fragmentation */
! 			if (P_RIGHTMOST(opaque) || opaque->btpo_next == blkno + 1)
  				btstat->continuous++;
  			else
! 				btstat->discontinuous++;
  
  			pgstat_index_page(stat, page, P_FIRSTDATAKEY(opaque),
  				PageGetMaxOffsetNumber(page));
*************** static Datum
*** 402,408 ****
  pgstat_hash(Relation rel, FunctionCallInfo fcinfo)
  {
  	pgstattuple_type	stat = { 0 };
! 	return pgstat_index(&stat, rel, HASH_METAPAGE + 1, pgstat_hash_page, fcinfo);
  }
  
  /*
--- 384,392 ----
  pgstat_hash(Relation rel, FunctionCallInfo fcinfo)
  {
  	pgstattuple_type	stat = { 0 };
! 	pgstat_index(&stat, rel, HASH_METAPAGE + 1, pgstat_hash_page);
! 	stat.fragmented_percent = -1;	/* not supported */
! 	return build_pgstattuple_type(&stat, fcinfo);
  }
  
  /*
*************** static Datum
*** 454,460 ****
  pgstat_gist(Relation rel, FunctionCallInfo fcinfo)
  {
  	pgstattuple_type	stat = { 0 };
! 	return pgstat_index(&stat, rel, GIST_ROOT_BLKNO + 1, pgstat_gist_page, fcinfo);
  }
  
  /*
--- 438,446 ----
  pgstat_gist(Relation rel, FunctionCallInfo fcinfo)
  {
  	pgstattuple_type	stat = { 0 };
! 	pgstat_index(&stat, rel, GIST_ROOT_BLKNO + 1, pgstat_gist_page);
! 	stat.fragmented_percent = -1;	/* not supported */
! 	return build_pgstattuple_type(&stat, fcinfo);
  }
  
  /*
*************** pgstat_gist_page(pgstattuple_type *stat,
*** 487,495 ****
  /*
   * 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;
--- 473,481 ----
  /*
   * pgstat_index -- returns live/dead tuples info in a generic index
   */
! static void
  pgstat_index(pgstattuple_type *stat, Relation rel, BlockNumber start,
! 	pgstat_page pagefn)
  {
  	BlockNumber nblocks;
  	BlockNumber blkno;
*************** pgstat_index(pgstattuple_type *stat, Rel
*** 514,521 ****
  	}
  
  	relation_close(rel, AccessShareLock);
- 
- 	return build_pgstattuple_type(stat, fcinfo);
  }
  
  /*
--- 500,505 ----
diff -cpr pgsql-orig/contrib/pgstattuple/pgstattuple.sql.in pgsql/contrib/pgstattuple/pgstattuple.sql.in
*** pgsql-orig/contrib/pgstattuple/pgstattuple.sql.in	Tue Feb 28 01:09:49 2006
--- pgsql/contrib/pgstattuple/pgstattuple.sql.in	Mon Jul 24 14:03:06 2006
*************** CREATE TYPE pgstattuple_type AS (
*** 10,16 ****
  	dead_tuple_len BIGINT,		-- total dead tuples length in bytes
  	dead_tuple_percent FLOAT,	-- dead tuples in %
  	free_space BIGINT,		-- free space in bytes
! 	free_percent FLOAT		-- free space in %
  );
  
  CREATE OR REPLACE FUNCTION pgstattuple(text)
--- 10,17 ----
  	dead_tuple_len BIGINT,		-- total dead tuples length in bytes
  	dead_tuple_percent FLOAT,	-- dead tuples in %
  	free_space BIGINT,		-- free space in bytes
! 	free_percent FLOAT,		-- free space in %
! 	fragmented_percent FLOAT	-- fragmented factor in %
  );
  
  CREATE OR REPLACE FUNCTION pgstattuple(text)
