Here's an update of Simon's Heap Page Diagnostic Functions. I now consider it ready to commit.

The new functions are all under new contrib module, contrib/pgdiagnostics. I also moved bt_metap, bt_page_items and bt_page_stats from pgstattuple to the new module.

I copied the bt_* functions almost unmodified. I didn't try to harmonize the user interface or return values with the new heap page functions, though that might we worth doing in the future.

I added input value checking to the new functions so that they should handle invalid data gracefully. All the new functions are nevertheless superuser-only, just to err on the safe side.

--
  Heikki Linnakangas
  EnterpriseDB   http://www.enterprisedb.com
Index: contrib/pgdiagnostics/Makefile
===================================================================
RCS file: contrib/pgdiagnostics/Makefile
diff -N contrib/pgdiagnostics/Makefile
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- contrib/pgdiagnostics/Makefile	2 May 2007 09:14:39 -0000
***************
*** 0 ****
--- 1,24 ----
+ #-------------------------------------------------------------------------
+ #
+ # pgdiagnostics Makefile
+ #
+ # $PostgreSQL$
+ #
+ #-------------------------------------------------------------------------
+ 
+ MODULE_big	= pgdiagnostics
+ OBJS		= rawpage.o heapfuncs.o btreefuncs.o
+ DOCS		= README.pgdiagnostics
+ DATA_built	= pgdiagnostics.sql
+ DATA      	= uninstall_pgdiagnostics.sql
+ 
+ ifdef USE_PGXS
+ PGXS := $(shell pg_config --pgxs)
+ include $(PGXS)
+ else
+ subdir = contrib/pgdiagnostics
+ top_builddir = ../..
+ include $(top_builddir)/src/Makefile.global
+ include $(top_srcdir)/contrib/contrib-global.mk
+ endif
+ 
Index: contrib/pgdiagnostics/README.pgdiagnostics
===================================================================
RCS file: contrib/pgdiagnostics/README.pgdiagnostics
diff -N contrib/pgdiagnostics/README.pgdiagnostics
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- contrib/pgdiagnostics/README.pgdiagnostics	2 May 2007 10:17:42 -0000
***************
*** 0 ****
--- 1,94 ----
+ The functions in this module allow you to inspect the contents of data pages
+ at a low level, for debugging purposes.
+ 
+ 1. Installation
+ 
+     $ make
+     $ make install
+     $ psql -e -f /usr/local/pgsql/share/contrib/pgdiagnostics.sql test
+ 
+ 2. Functions included:
+ 
+     get_raw_page
+     ------------
+     get_raw_page reads one block of the named table and returns a copy as a
+     bytea field. This allows a single time-consistent copy of the block to be
+     made. Use of this functions is restricted to superusers.
+ 
+     page_header
+     -----------
+     page_header shows fields which are common to all PostgreSQL heap and index
+     pages. Use of this function is restricted to superusers.
+ 
+     A page image obtained with get_raw_page should be passed as argument:
+ 
+         test=# SELECT * FROM page_header(get_raw_page('pg_class',0));
+            lsn    | tli | flags | lower | upper | special | pagesize | version
+         ----------+-----+-------+-------+-------+---------+----------+---------
+          0/3C5614 |   1 |     1 |   216 |   256 |    8192 |     8192 |       4
+         (1 row)
+ 
+     The returned columns correspond to the fields in the PageHeaderData-struct,
+     see src/include/storage/bufpage.h for more details.
+ 
+     heap_page_items
+     ---------------
+     heap_page_items shows all line pointers on a heap page.  For those line
+     pointers that are in use, tuple headers are also shown. All tuples are
+     shown, whether or not the tuples were visible to an MVCC snapshot at the
+     time the raw page was copied. Use of this function is restricted to
+     superusers.
+ 
+     A heap page image obtained with get_raw_page should be passed as argument:
+ 
+         test=# SELECT * FROM heap_page_items(get_raw_page('pg_class',0));
+ 
+     See src/include/storage/itemid.h and src/include/access/htup.h for
+     explanations of the fields returned.
+ 
+     bt_metap
+     --------
+     bt_metap() returns information about the btree index metapage:
+ 
+         test=> SELECT * FROM bt_metap('pg_cast_oid_index');
+         -[ RECORD 1 ]-----
+         magic     | 340322
+         version   | 2
+         root      | 1
+         level     | 0
+         fastroot  | 1
+         fastlevel | 0
+ 
+     bt_page_stats
+     -------------
+     bt_page_stats() shows information about single btree pages:
+ 
+         test=> SELECT * FROM bt_page_stats('pg_cast_oid_index', 1);
+         -[ RECORD 1 ]-+-----
+         blkno         | 1
+         type          | l
+         live_items    | 256
+         dead_items    | 0
+         avg_item_size | 12
+         page_size     | 8192
+         free_size     | 4056
+         btpo_prev     | 0
+         btpo_next     | 0
+         btpo          | 0
+         btpo_flags    | 3
+ 
+     bt_page_items
+     -------------
+     bt_page_items() returns information about specific items on btree pages:
+ 
+         test=> SELECT * FROM bt_page_items('pg_cast_oid_index', 1);
+          itemoffset |  ctid   | itemlen | nulls | vars |    data
+         ------------+---------+---------+-------+------+-------------
+                   1 | (0,1)   |      12 | f     | f    | 23 27 00 00
+                   2 | (0,2)   |      12 | f     | f    | 24 27 00 00
+                   3 | (0,3)   |      12 | f     | f    | 25 27 00 00
+                   4 | (0,4)   |      12 | f     | f    | 26 27 00 00
+                   5 | (0,5)   |      12 | f     | f    | 27 27 00 00
+                   6 | (0,6)   |      12 | f     | f    | 28 27 00 00
+                   7 | (0,7)   |      12 | f     | f    | 29 27 00 00
+                   8 | (0,8)   |      12 | f     | f    | 2a 27 00 00
Index: contrib/pgdiagnostics/btreefuncs.c
===================================================================
RCS file: contrib/pgdiagnostics/btreefuncs.c
diff -N contrib/pgdiagnostics/btreefuncs.c
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- contrib/pgdiagnostics/btreefuncs.c	1 May 2007 12:36:31 -0000
***************
*** 0 ****
--- 1,499 ----
+ /*
+  * btreefuncs.c
+  *
+  * Copyright (c) 2006 Satoshi Nagayasu <[EMAIL PROTECTED]>
+  *
+  * Permission to use, copy, modify, and distribute this software and
+  * its documentation for any purpose, without fee, and without a
+  * written agreement is hereby granted, provided that the above
+  * copyright notice and this paragraph and the following two
+  * paragraphs appear in all copies.
+  *
+  * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT,
+  * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+  * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+  * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED
+  * OF THE POSSIBILITY OF SUCH DAMAGE.
+  *
+  * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+  * A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS
+  * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
+  * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "fmgr.h"
+ #include "funcapi.h"
+ #include "access/heapam.h"
+ #include "access/itup.h"
+ #include "access/nbtree.h"
+ #include "access/transam.h"
+ #include "catalog/namespace.h"
+ #include "catalog/pg_type.h"
+ #include "utils/builtins.h"
+ #include "utils/inval.h"
+ 
+ PG_FUNCTION_INFO_V1(bt_metap);
+ PG_FUNCTION_INFO_V1(bt_page_items);
+ PG_FUNCTION_INFO_V1(bt_page_stats);
+ 
+ extern Datum bt_metap(PG_FUNCTION_ARGS);
+ extern Datum bt_page_items(PG_FUNCTION_ARGS);
+ extern Datum bt_page_stats(PG_FUNCTION_ARGS);
+ 
+ #define BTMETAP_TYPE "public.bt_metap_type"
+ #define BTMETAP_NCOLUMNS 6
+ 
+ #define BTPAGEITEMS_TYPE "public.bt_page_items_type"
+ #define BTPAGEITEMS_NCOLUMNS 6
+ 
+ #define BTPAGESTATS_TYPE "public.bt_page_stats_type"
+ #define BTPAGESTATS_NCOLUMNS 11
+ 
+ 
+ #define IS_INDEX(r) ((r)->rd_rel->relkind == 'i')
+ #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
+ 
+ #define CHECK_PAGE_OFFSET_RANGE(page, offset) { \
+ 		if ( !(FirstOffsetNumber<=(offset) && \
+ 						(offset)<=PageGetMaxOffsetNumber(page)) ) \
+ 			 elog(ERROR, "Page offset number out of range."); }
+ 
+ #define CHECK_RELATION_BLOCK_RANGE(rel, blkno) { \
+ 		if ( (blkno)<0 && RelationGetNumberOfBlocks((rel))<=(blkno) ) \
+ 			 elog(ERROR, "Block number out of range."); }
+ 
+ /* ------------------------------------------------
+  * structure for single btree page statistics
+  * ------------------------------------------------
+  */
+ typedef struct BTPageStat
+ {
+ 	uint32		blkno;
+ 	uint32		live_items;
+ 	uint32		dead_items;
+ 	uint32		page_size;
+ 	uint32		max_avail;
+ 	uint32		free_size;
+ 	uint32		avg_item_size;
+ 	char		type;
+ 
+ 	/* opaque data */
+ 	BlockNumber btpo_prev;
+ 	BlockNumber btpo_next;
+ 	union
+ 	{
+ 		uint32		level;
+ 		TransactionId xact;
+ 	}			btpo;
+ 	uint16		btpo_flags;
+ 	BTCycleId	btpo_cycleid;
+ }	BTPageStat;
+ 
+ /* ------------------------------------------------
+  * A structure for a whole btree index statistics
+  * used by pgstatindex().
+  * ------------------------------------------------
+  */
+ typedef struct BTIndexStat
+ {
+ 	uint32		magic;
+ 	uint32		version;
+ 	BlockNumber root_blkno;
+ 	uint32		level;
+ 
+ 	BlockNumber fastroot;
+ 	uint32		fastlevel;
+ 
+ 	uint32		live_items;
+ 	uint32		dead_items;
+ 
+ 	uint32		root_pages;
+ 	uint32		internal_pages;
+ 	uint32		leaf_pages;
+ 	uint32		empty_pages;
+ 	uint32		deleted_pages;
+ 
+ 	uint32		page_size;
+ 	uint32		avg_item_size;
+ 
+ 	uint32		max_avail;
+ 	uint32		free_space;
+ }	BTIndexStat;
+ 
+ 
+ /* -------------------------------------------------
+  * GetBTPageStatistics()
+  *
+  * Collect statistics of single b-tree leaf page
+  * -------------------------------------------------
+  */
+ static void
+ GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat * stat)
+ {
+ 	Page		page = BufferGetPage(buffer);
+ 	PageHeader	phdr = (PageHeader) page;
+ 	OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
+ 	BTPageOpaque opaque = (BTPageOpaque) PageGetSpecialPointer(page);
+ 	int			item_size = 0;
+ 	int			off;
+ 
+ 	stat->blkno = blkno;
+ 
+ 	stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
+ 
+ 	stat->dead_items = stat->live_items = 0;
+ 
+ 	stat->page_size = PageGetPageSize(page);
+ 
+ 	/* page type (flags) */
+ 	if (P_ISDELETED(opaque))
+ 	{
+ 		stat->type = 'd';
+ 		stat->btpo.xact = opaque->btpo.xact;
+ 		return;
+ 	}
+ 	else if (P_IGNORE(opaque))
+ 		stat->type = 'e';
+ 	else if (P_ISLEAF(opaque))
+ 		stat->type = 'l';
+ 	else if (P_ISROOT(opaque))
+ 		stat->type = 'r';
+ 	else
+ 		stat->type = 'i';
+ 
+ 	/* btpage opaque data */
+ 	stat->btpo_prev = opaque->btpo_prev;
+ 	stat->btpo_next = opaque->btpo_next;
+ 	stat->btpo.level = opaque->btpo.level;
+ 	stat->btpo_flags = opaque->btpo_flags;
+ 	stat->btpo_cycleid = opaque->btpo_cycleid;
+ 
+ 	/* count live and dead tuples, and free space */
+ 	for (off = FirstOffsetNumber; off <= maxoff; off++)
+ 	{
+ 		IndexTuple	itup;
+ 
+ 		ItemId		id = PageGetItemId(page, off);
+ 
+ 		itup = (IndexTuple) PageGetItem(page, id);
+ 
+ 		item_size += IndexTupleSize(itup);
+ 
+ 		if (!ItemIdDeleted(id))
+ 			stat->live_items++;
+ 		else
+ 			stat->dead_items++;
+ 	}
+ 	stat->free_size = PageGetFreeSpace(page);
+ 
+ 	if ((stat->live_items + stat->dead_items) > 0)
+ 		stat->avg_item_size = item_size / (stat->live_items + stat->dead_items);
+ 	else
+ 		stat->avg_item_size = 0;
+ }
+ 
+ /* -----------------------------------------------
+  * bt_page()
+  *
+  * Usage: SELECT * FROM bt_page('t1_pkey', 0);
+  * -----------------------------------------------
+  */
+ Datum
+ bt_page_stats(PG_FUNCTION_ARGS)
+ {
+ 	text	   *relname = PG_GETARG_TEXT_P(0);
+ 	uint32		blkno = PG_GETARG_UINT32(1);
+ 	Buffer		buffer;
+ 
+ 	Relation	rel;
+ 	RangeVar   *relrv;
+ 	Datum		result;
+ 
+ 	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ 	rel = relation_openrv(relrv, AccessShareLock);
+ 
+ 	CHECK_RELATION_BLOCK_RANGE(rel, blkno);
+ 
+ 	buffer = ReadBuffer(rel, blkno);
+ 
+ 	if (!IS_INDEX(rel) || !IS_BTREE(rel))
+ 		elog(ERROR, "bt_page_stats() can be used only on b-tree index.");
+ 
+ 	if (blkno == 0)
+ 		elog(ERROR, "Block 0 is a meta page.");
+ 
+ 	{
+ 		HeapTuple	tuple;
+ 		TupleDesc	tupleDesc;
+ 		int			j;
+ 		char	   *values[BTPAGESTATS_NCOLUMNS];
+ 
+ 		BTPageStat	stat;
+ 
+ 		/* keep compiler quiet */
+ 		stat.btpo_prev = stat.btpo_next = InvalidBlockNumber;
+ 		stat.btpo_flags = stat.free_size = stat.avg_item_size = 0;
+ 
+ 		GetBTPageStatistics(blkno, buffer, &stat);
+ 
+ 		tupleDesc = RelationNameGetTupleDesc(BTPAGESTATS_TYPE);
+ 
+ 		j = 0;
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", stat.blkno);
+ 
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%c", stat.type);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", stat.live_items);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", stat.dead_items);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", stat.avg_item_size);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", stat.page_size);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", stat.free_size);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", stat.btpo_prev);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", stat.btpo_next);
+ 
+ 		values[j] = palloc(32);
+ 		if (stat.type == 'd')
+ 			snprintf(values[j++], 32, "%d", stat.btpo.xact);
+ 		else
+ 			snprintf(values[j++], 32, "%d", stat.btpo.level);
+ 
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", stat.btpo_flags);
+ 
+ 		tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ 									   values);
+ 
+ 		result = TupleGetDatum(TupleDescGetSlot(tupleDesc), tuple);
+ 	}
+ 
+ 	ReleaseBuffer(buffer);
+ 
+ 	relation_close(rel, AccessShareLock);
+ 
+ 	PG_RETURN_DATUM(result);
+ }
+ 
+ /*-------------------------------------------------------
+  * bt_page_items()
+  *
+  * Get IndexTupleData set in a leaf page
+  *
+  * Usage: SELECT * FROM bt_page_items('t1_pkey', 0);
+  *-------------------------------------------------------
+  */
+ /* ---------------------------------------------------
+  * data structure for SRF to hold a scan information
+  * ---------------------------------------------------
+  */
+ struct user_args
+ {
+ 	TupleDesc	tupd;
+ 	Relation	rel;
+ 	Buffer		buffer;
+ 	Page		page;
+ 	uint16		offset;
+ };
+ 
+ Datum
+ bt_page_items(PG_FUNCTION_ARGS)
+ {
+ 	text	   *relname = PG_GETARG_TEXT_P(0);
+ 	uint32		blkno = PG_GETARG_UINT32(1);
+ 
+ 	RangeVar   *relrv;
+ 	Datum		result;
+ 	char	   *values[BTPAGEITEMS_NCOLUMNS];
+ 	BTPageOpaque opaque;
+ 	HeapTuple	tuple;
+ 	ItemId		id;
+ 
+ 	FuncCallContext *fctx;
+ 	MemoryContext mctx;
+ 	struct user_args *uargs = NULL;
+ 
+ 	if (blkno == 0)
+ 		elog(ERROR, "Block 0 is a meta page.");
+ 
+ 	if (SRF_IS_FIRSTCALL())
+ 	{
+ 		fctx = SRF_FIRSTCALL_INIT();
+ 		mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+ 
+ 		uargs = palloc(sizeof(struct user_args));
+ 
+ 		uargs->tupd = RelationNameGetTupleDesc(BTPAGEITEMS_TYPE);
+ 		uargs->offset = FirstOffsetNumber;
+ 
+ 		relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ 		uargs->rel = relation_openrv(relrv, AccessShareLock);
+ 
+ 		CHECK_RELATION_BLOCK_RANGE(uargs->rel, blkno);
+ 
+ 		uargs->buffer = ReadBuffer(uargs->rel, blkno);
+ 
+ 		if (!IS_INDEX(uargs->rel) || !IS_BTREE(uargs->rel))
+ 			elog(ERROR, "bt_page_items() can be used only on b-tree index.");
+ 
+ 		uargs->page = BufferGetPage(uargs->buffer);
+ 
+ 		opaque = (BTPageOpaque) PageGetSpecialPointer(uargs->page);
+ 
+ 		if (P_ISDELETED(opaque))
+ 			elog(NOTICE, "bt_page_items(): this page is deleted.");
+ 
+ 		fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
+ 		fctx->user_fctx = uargs;
+ 
+ 		MemoryContextSwitchTo(mctx);
+ 	}
+ 
+ 	fctx = SRF_PERCALL_SETUP();
+ 	uargs = fctx->user_fctx;
+ 
+ 	if (fctx->call_cntr < fctx->max_calls)
+ 	{
+ 		IndexTuple	itup;
+ 
+ 		id = PageGetItemId(uargs->page, uargs->offset);
+ 
+ 		if (!ItemIdIsValid(id))
+ 			elog(ERROR, "Invalid ItemId.");
+ 
+ 		itup = (IndexTuple) PageGetItem(uargs->page, id);
+ 
+ 		{
+ 			int			j = 0;
+ 
+ 			BlockNumber blkno = BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid));
+ 
+ 			values[j] = palloc(32);
+ 			snprintf(values[j++], 32, "%d", uargs->offset);
+ 			values[j] = palloc(32);
+ 			snprintf(values[j++], 32, "(%u,%u)", blkno, itup->t_tid.ip_posid);
+ 			values[j] = palloc(32);
+ 			snprintf(values[j++], 32, "%d", (int) IndexTupleSize(itup));
+ 			values[j] = palloc(32);
+ 			snprintf(values[j++], 32, "%c", IndexTupleHasNulls(itup) ? 't' : 'f');
+ 			values[j] = palloc(32);
+ 			snprintf(values[j++], 32, "%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
+ 
+ 			{
+ 				int			off;
+ 				char	   *dump;
+ 				char	   *ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ 
+ 				dump = palloc(IndexTupleSize(itup) * 3);
+ 				memset(dump, 0, IndexTupleSize(itup) * 3);
+ 
+ 				for (off = 0;
+ 					 off < IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ 					 off++)
+ 				{
+ 					if (dump[0] == '\0')
+ 						sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ 					else
+ 					{
+ 						char		buf[4];
+ 
+ 						sprintf(buf, " %02x", *(ptr + off) & 0xff);
+ 						strcat(dump, buf);
+ 					}
+ 				}
+ 				values[j] = dump;
+ 			}
+ 
+ 			tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(uargs->tupd), values);
+ 			result = TupleGetDatum(TupleDescGetSlot(uargs->tupd), tuple);
+ 		}
+ 
+ 		uargs->offset = uargs->offset + 1;
+ 
+ 		SRF_RETURN_NEXT(fctx, result);
+ 	}
+ 	else
+ 	{
+ 		ReleaseBuffer(uargs->buffer);
+ 		relation_close(uargs->rel, AccessShareLock);
+ 
+ 		SRF_RETURN_DONE(fctx);
+ 	}
+ }
+ 
+ 
+ /* ------------------------------------------------
+  * bt_metap()
+  *
+  * Get a btree meta-page information
+  *
+  * Usage: SELECT * FROM bt_metap('t1_pkey')
+  * ------------------------------------------------
+  */
+ Datum
+ bt_metap(PG_FUNCTION_ARGS)
+ {
+ 	text	   *relname = PG_GETARG_TEXT_P(0);
+ 	Buffer		buffer;
+ 
+ 	Relation	rel;
+ 	RangeVar   *relrv;
+ 	Datum		result;
+ 
+ 	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ 	rel = relation_openrv(relrv, AccessShareLock);
+ 
+ 	if (!IS_INDEX(rel) || !IS_BTREE(rel))
+ 		elog(ERROR, "bt_metap() can be used only on b-tree index.");
+ 
+ 	buffer = ReadBuffer(rel, 0);
+ 
+ 	{
+ 		BTMetaPageData *metad;
+ 
+ 		TupleDesc	tupleDesc;
+ 		int			j;
+ 		char	   *values[BTMETAP_NCOLUMNS];
+ 		HeapTuple	tuple;
+ 
+ 		Page		page = BufferGetPage(buffer);
+ 
+ 		metad = BTPageGetMeta(page);
+ 
+ 		tupleDesc = RelationNameGetTupleDesc(BTMETAP_TYPE);
+ 
+ 		j = 0;
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", metad->btm_magic);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", metad->btm_version);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", metad->btm_root);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", metad->btm_level);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", metad->btm_fastroot);
+ 		values[j] = palloc(32);
+ 		snprintf(values[j++], 32, "%d", metad->btm_fastlevel);
+ 
+ 		tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ 									   values);
+ 
+ 		result = TupleGetDatum(TupleDescGetSlot(tupleDesc), tuple);
+ 	}
+ 
+ 	ReleaseBuffer(buffer);
+ 
+ 	relation_close(rel, AccessShareLock);
+ 
+ 	PG_RETURN_DATUM(result);
+ }
Index: contrib/pgdiagnostics/heapfuncs.c
===================================================================
RCS file: contrib/pgdiagnostics/heapfuncs.c
diff -N contrib/pgdiagnostics/heapfuncs.c
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- contrib/pgdiagnostics/heapfuncs.c	2 May 2007 10:26:14 -0000
***************
*** 0 ****
--- 1,229 ----
+ /*-------------------------------------------------------------------------
+  *
+  * heapfuncs.c
+  *	  Functions to investigate heap pages
+  *
+  * We check the input to these functions for corrupt pointers etc. that
+  * might cause crashes, but at the same time we try to print out as much
+  * information as possible, even if it's nonsense. That's because if a
+  * page is corrupt, we don't know why and how exactly it is corrupt, so we
+  * let the user to judge it.
+  * 
+  * These functions are restricted to superusers for the fear of introducing
+  * security holes if the input checking isn't as water-tight as it should. 
+  * You'd need to be superuser to obtain a raw page image anyway, so 
+  * there's hardly any use case for using these without superuser-rights
+  * anyway.
+  *
+  * Copyright (c) 2007, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *	  $PostgreSQL$
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "fmgr.h"
+ #include "funcapi.h"
+ #include "access/heapam.h"
+ #include "access/transam.h"
+ #include "catalog/namespace.h"
+ #include "catalog/pg_type.h"
+ #include "utils/builtins.h"
+ #include "miscadmin.h"
+ 
+ Datum heap_page_items(PG_FUNCTION_ARGS);
+ 
+ #define GET_TEXT(str_) \
+         DirectFunctionCall1(textin, CStringGetDatum(str_))
+ 
+ /*
+  * bits_to_text
+  *
+  * Converts a bits8-array of 'len' bits to a human-readable
+  * c-string representation.
+  */
+ static char *
+ bits_to_text(bits8 *bits, int len)
+ {
+ 	int i;
+ 	char *str;
+ 
+ 	str = palloc(len + 1);
+ 	
+ 	for(i = 0; i < len; i++)
+ 		str[i] = (bits[(i / 8)] & (1 << (i % 8))) ? '1' : '0';
+ 
+ 	str[i] = '\0';
+ 
+ 	return str;
+ }
+ 
+ 
+ /*
+  * heap_page_items
+  *
+  * Allows inspection of line pointers and tuple headers of a heap page.
+  */
+ PG_FUNCTION_INFO_V1(heap_page_items);
+ 
+ typedef struct heap_page_items_state
+ {
+ 	TupleDesc	tupd;
+ 	Page		page;
+ 	uint16		offset;
+ } heap_page_items_state;
+ 
+ Datum
+ heap_page_items(PG_FUNCTION_ARGS)
+ {
+ 	bytea  *raw_page = PG_GETARG_BYTEA_P(0);
+ 	heap_page_items_state *inter_call_data = NULL;
+ 	FuncCallContext *fctx;
+ 	int		raw_page_size;
+ 
+ 	if (!superuser())
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 				 (errmsg("must be superuser to use raw page functions"))));
+ 
+ 	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+ 
+ 	if (SRF_IS_FIRSTCALL())
+ 	{
+ 		TupleDesc	tupdesc;
+ 		MemoryContext mctx;
+ 
+ 		if(raw_page_size < SizeOfPageHeaderData)
+ 			ereport(ERROR, 
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("input page too small (%d bytes)", raw_page_size)));
+ 
+ 		fctx = SRF_FIRSTCALL_INIT();
+ 		mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+ 
+ 		inter_call_data = palloc(sizeof(heap_page_items_state));
+ 
+ 		/* 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");
+ 
+ 		inter_call_data->tupd = tupdesc;
+ 
+ 		inter_call_data->offset = FirstOffsetNumber;
+ 		inter_call_data->page = VARDATA(raw_page);
+ 
+ 		fctx->max_calls = PageGetMaxOffsetNumber(inter_call_data->page);
+ 		fctx->user_fctx = inter_call_data;
+ 
+ 		MemoryContextSwitchTo(mctx);
+ 	}
+ 
+ 	fctx = SRF_PERCALL_SETUP();
+ 	inter_call_data = fctx->user_fctx;
+ 
+ 	if (fctx->call_cntr < fctx->max_calls)
+ 	{
+ 		Page		page = inter_call_data->page;
+ 		HeapTuple	resultTuple;
+ 		Datum		result;
+ 		ItemId		id;
+ 		Datum		values[13];
+ 		bool		nulls[13];
+ 		uint16 		lp_offset;
+ 		uint16		lp_flags;
+ 		uint16		lp_len;
+ 
+ 		memset(nulls, 0, sizeof(nulls));
+ 
+ 		/* Extract information from the line pointer */
+ 		
+ 		id = PageGetItemId(page, inter_call_data->offset);
+ 
+ 		lp_offset	= ItemIdGetOffset(id);
+ 		lp_flags	= ItemIdGetFlags(id);
+ 		lp_len		= ItemIdGetLength(id);
+ 
+ 		values[0] = UInt16GetDatum(inter_call_data->offset);
+ 		values[1] = UInt16GetDatum(lp_offset);
+ 		values[2] = UInt16GetDatum(lp_flags);
+ 		values[3] = UInt16GetDatum(lp_len);
+ 
+ 		/* We do just enough validity checking to make sure we don't 
+ 		 * reference data outside the page passed to us. The page
+ 		 * could be corrupt in many other ways, but at least we won't 
+ 		 * crash.
+ 		 */
+ 		if ((lp_len >= sizeof(HeapTupleHeader)) &&
+ 			(lp_offset == MAXALIGN(lp_offset)) &&
+ 			(lp_offset + lp_len <= raw_page_size) &&
+ 			ItemIdIsUsed(id))
+ 		{
+ 			HeapTupleHeader	tuphdr;
+ 			int				bits_len;
+ 
+ 			/* Extract infromation from the tuple header */
+ 
+ 			tuphdr = (HeapTupleHeader) PageGetItem(page, id);
+ 		
+ 			values[4] = UInt32GetDatum(HeapTupleHeaderGetXmin(tuphdr));
+ 			values[5] = UInt32GetDatum(HeapTupleHeaderGetXmax(tuphdr));
+ 			values[6] = UInt32GetDatum(HeapTupleHeaderGetRawCommandId(tuphdr)); /* shared with xvac */
+ 			values[7] = PointerGetDatum(&tuphdr->t_ctid);
+ 			values[8] = UInt16GetDatum(tuphdr->t_infomask2);
+ 			values[9] = UInt16GetDatum(tuphdr->t_infomask);
+ 			values[10] = UInt8GetDatum(tuphdr->t_hoff);
+ 
+ 			/* We already checked that the item as is completely within
+ 			 * the raw page passed to us, with the length given in the line
+ 			 * pointer.. Let's check that t_hoff doesn't point over lp_len,
+ 			 * before using it to access t_bits and oid.
+ 			 */
+ 			if (tuphdr->t_hoff >= sizeof(HeapTupleHeader) && 
+ 				tuphdr->t_hoff <= lp_len)
+ 			{
+ 				if (tuphdr->t_infomask & HEAP_HASNULL)
+ 				{
+ 					bits_len = tuphdr->t_hoff - 
+ 						(((char *)tuphdr->t_bits) - ((char *)tuphdr));
+ 
+ 					values[11] = GET_TEXT(
+ 						bits_to_text(tuphdr->t_bits, bits_len * 8));
+ 				} 
+ 				else
+ 					nulls[11] = true;
+ 
+ 				if (tuphdr->t_infomask & HEAP_HASOID)
+ 					values[12] = HeapTupleHeaderGetOid(tuphdr);
+ 				else
+ 					nulls[12] = true;
+ 			}
+ 			else
+ 			{
+ 				nulls[11] = true;
+ 				nulls[12] = true;
+ 			}
+ 		}
+ 		else
+ 		{
+ 			/* The line pointer is not used, or it's invalid. Set the rest of
+ 			 * the fields to NULL */
+ 			int i;
+ 
+ 			for(i = 4; i <= 12; i++)
+ 				nulls[i] = true;
+ 		}
+ 
+         /* Build and return the result tuple. */
+         resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
+         result = HeapTupleGetDatum(resultTuple);
+ 
+ 		inter_call_data->offset++;
+ 
+ 		SRF_RETURN_NEXT(fctx, result);
+ 	}
+ 	else
+ 		SRF_RETURN_DONE(fctx);
+ }
Index: contrib/pgdiagnostics/pgdiagnostics.sql.in
===================================================================
RCS file: contrib/pgdiagnostics/pgdiagnostics.sql.in
diff -N contrib/pgdiagnostics/pgdiagnostics.sql.in
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- contrib/pgdiagnostics/pgdiagnostics.sql.in	2 May 2007 10:09:56 -0000
***************
*** 0 ****
--- 1,109 ----
+ -- Adjust this setting to control where the objects get created.
+ SET search_path = public;
+ 
+ --
+ -- get_raw_page()
+ --
+ CREATE OR REPLACE FUNCTION get_raw_page(text, int4)
+ RETURNS bytea
+ AS 'MODULE_PATHNAME', 'get_raw_page'
+ LANGUAGE C STRICT;
+ 
+ --
+ -- page_header()
+ --
+ CREATE TYPE page_header_type AS (
+ 	lsn text,
+ 	tli smallint,
+ 	flags smallint,
+ 	lower smallint,
+ 	upper smallint,
+ 	special smallint,
+ 	pagesize smallint,
+ 	version smallint
+ );
+ 
+ CREATE OR REPLACE FUNCTION page_header(bytea)
+ RETURNS page_header_type
+ AS 'MODULE_PATHNAME', 'page_header'
+ LANGUAGE C STRICT;
+ 
+ --
+ -- heap_page_items()
+ --
+ CREATE TYPE heap_page_items_type AS (
+ 	lp smallint,
+ 	lp_off smallint,
+ 	lp_flags smallint,
+ 	lp_len smallint,
+ 	t_xmin xid,
+ 	t_xmax xid,
+ 	t_field3 int4,
+ 	t_ctid tid,
+ 	t_infomask2 smallint,
+ 	t_infomask smallint,
+ 	t_hoff smallint,
+ 	t_bits text,
+ 	t_oid oid
+ );
+ 
+ CREATE OR REPLACE FUNCTION heap_page_items(bytea)
+ RETURNS SETOF heap_page_items_type
+ AS 'MODULE_PATHNAME', 'heap_page_items'
+ LANGUAGE C STRICT;
+ 
+ --
+ -- bt_metap()
+ --
+ CREATE TYPE bt_metap_type AS (
+   magic int4,
+   version int4,
+   root int4,
+   level int4,
+   fastroot int4,
+   fastlevel int4
+ );
+ 
+ CREATE OR REPLACE FUNCTION bt_metap(text)
+ RETURNS bt_metap_type
+ AS 'MODULE_PATHNAME', 'bt_metap'
+ LANGUAGE 'C' STRICT;
+ 
+ --
+ -- bt_page_stats()
+ --
+ CREATE TYPE bt_page_stats_type AS (
+   blkno int4,
+   type char,
+   live_items int4,
+   dead_items int4,
+   avg_item_size float,
+   page_size int4,
+   free_size int4,
+   btpo_prev int4,
+   btpo_next int4,
+   btpo int4,
+   btpo_flags int4
+ );
+ 
+ CREATE OR REPLACE FUNCTION bt_page_stats(text, int4)
+ RETURNS bt_page_stats_type
+ AS 'MODULE_PATHNAME', 'bt_page_stats'
+ LANGUAGE 'C' STRICT;
+ 
+ --
+ -- bt_page_items()
+ --
+ CREATE TYPE bt_page_items_type AS (
+   itemoffset smallint,
+   ctid tid,
+   itemlen smallint,
+   nulls bool,
+   vars bool,
+   data text
+ );
+ 
+ CREATE OR REPLACE FUNCTION bt_page_items(text, int4)
+ RETURNS SETOF bt_page_items_type
+ AS 'MODULE_PATHNAME', 'bt_page_items'
+ LANGUAGE 'C' STRICT;
Index: contrib/pgdiagnostics/rawpage.c
===================================================================
RCS file: contrib/pgdiagnostics/rawpage.c
diff -N contrib/pgdiagnostics/rawpage.c
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- contrib/pgdiagnostics/rawpage.c	2 May 2007 10:08:13 -0000
***************
*** 0 ****
--- 1,164 ----
+ /*-------------------------------------------------------------------------
+  *
+  * rawpage.c
+  *	  Functions to extract a raw page as bytea and inspect it
+  *
+  * Access-method specific inspection functions are in separate files.
+  *
+  * Copyright (c) 2007, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *	  $PostgreSQL$
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "fmgr.h"
+ #include "funcapi.h"
+ #include "access/heapam.h"
+ #include "access/transam.h"
+ #include "catalog/namespace.h"
+ #include "catalog/pg_type.h"
+ #include "utils/builtins.h"
+ #include "miscadmin.h"
+ 
+ PG_MODULE_MAGIC;
+ 
+ Datum get_raw_page(PG_FUNCTION_ARGS);
+ Datum page_header(PG_FUNCTION_ARGS);
+ 
+ /*
+  * heap_get_raw_page
+  *
+  * Returns a copy of a page from shared buffers as a bytea
+  */
+ PG_FUNCTION_INFO_V1(get_raw_page);
+ 
+ Datum
+ get_raw_page(PG_FUNCTION_ARGS)
+ {
+ 	text	   *relname = PG_GETARG_TEXT_P(0);
+ 	uint32		blkno = PG_GETARG_UINT32(1);
+ 
+ 	Relation	rel;
+ 	RangeVar	*relrv;
+ 	bytea		*raw_page;
+ 	char		*raw_page_data;
+ 	Buffer		buf;
+ 
+ 	if (!superuser())
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 				 (errmsg("must be superuser to use raw functions"))));
+ 
+ 	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ 	rel = relation_openrv(relrv, AccessShareLock);
+ 
+ 	/* Check that this relation has storage */
+ 	if (rel->rd_rel->relkind == RELKIND_VIEW)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("cannot get raw page from view \"%s\"",
+ 							RelationGetRelationName(rel))));
+ 	if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("cannot get raw page from composite type \"%s\"",
+ 							RelationGetRelationName(rel))));
+ 
+ 	if (blkno >= RelationGetNumberOfBlocks(rel))
+ 		elog(ERROR, "block number %u is out of range for relation \"%s\"",
+ 			 blkno, RelationGetRelationName(rel));
+ 
+ 	/* Initialize buffer to copy to */
+ 	raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ 	SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ 	raw_page_data = VARDATA(raw_page);
+ 
+ 	/* Take a verbatim copy of the page */
+ 
+ 	buf = ReadBuffer(rel, blkno);
+ 	LockBuffer(buf, BUFFER_LOCK_SHARE);
+ 
+ 	memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+ 
+ 	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ 	ReleaseBuffer(buf);
+ 
+ 	relation_close(rel, AccessShareLock);
+ 
+ 	PG_RETURN_BYTEA_P(raw_page);
+ }
+ 
+ /*
+  * page_header
+  *
+  * Allows inspection of page header fields of a raw page
+  */
+ 
+ PG_FUNCTION_INFO_V1(page_header);
+ 
+ Datum
+ page_header(PG_FUNCTION_ARGS)
+ {
+ 	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+ 	int			raw_page_size;
+ 
+ 	TupleDesc	tupdesc;
+ 
+ 	Datum		result;
+ 	HeapTuple	tuple;
+ 	Datum		values[8];
+ 	bool		nulls[8];
+ 
+ 	PageHeader	page;
+ 	XLogRecPtr	lsn;
+ 	char		lsnchar[64];
+ 
+ 	if (!superuser())
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 				 (errmsg("must be superuser to use raw page functions"))));
+ 
+ 	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+ 
+ 	/*
+ 	 * Check that enough data was supplied, so that we don't try to access 
+ 	 * fields outside the supplied buffer. 
+ 	 */
+ 	if(raw_page_size < sizeof(PageHeaderData))
+ 		ereport(ERROR, 
+ 			 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 			  errmsg("input page too small (%d bytes)", raw_page_size)));
+ 
+ 	page = (PageHeader) VARDATA(raw_page);
+ 
+ 	/* 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");
+ 
+ 	/* Extract information from the page header */
+ 
+ 	lsn = PageGetLSN(page);
+ 	snprintf(lsnchar, sizeof(lsnchar), "%X/%X", lsn.xlogid, lsn.xrecoff);
+ 
+ 	values[0] = DirectFunctionCall1(textin, CStringGetDatum(lsnchar));
+ 	values[1] = UInt16GetDatum(PageGetTLI(page));
+ 	values[2] = UInt16GetDatum(page->pd_flags);
+ 	values[3] = UInt16GetDatum(page->pd_lower);
+ 	values[4] = UInt16GetDatum(page->pd_upper);
+ 	values[5] = UInt16GetDatum(page->pd_special);
+ 	values[6] = UInt16GetDatum(PageGetPageSize(page));
+ 	values[7] = UInt16GetDatum(PageGetPageLayoutVersion(page));
+ 
+     /* Build and return the tuple. */
+ 
+ 	memset(nulls, 0, sizeof(nulls));
+ 
+     tuple = heap_form_tuple(tupdesc, values, nulls);
+     result = HeapTupleGetDatum(tuple);
+ 
+ 	PG_RETURN_DATUM(result);
+ }
Index: contrib/pgdiagnostics/uninstall_pgdiagnostics.sql
===================================================================
RCS file: contrib/pgdiagnostics/uninstall_pgdiagnostics.sql
diff -N contrib/pgdiagnostics/uninstall_pgdiagnostics.sql
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- contrib/pgdiagnostics/uninstall_pgdiagnostics.sql	1 May 2007 13:06:17 -0000
***************
*** 0 ****
--- 1,20 ----
+ -- Adjust this setting to control where the objects get created.
+ SET search_path = public;
+ 
+ DROP FUNCTION get_raw_page(text, int4);
+ 
+ DROP FUNCTION page_header(bytea);
+ DROP TYPE page_header_type;
+ 
+ DROP FUNCTION heap_page_items(bytea);
+ DROP TYPE heap_page_items_type;
+ 
+ DROP FUNCTION bt_metap(text);
+ DROP TYPE bt_metap_type;
+ 
+ DROP FUNCTION bt_page_stats(text, int4);
+ DROP TYPE bt_page_stats_type;
+ 
+ DROP FUNCTION bt_page_items(text, int4);
+ DROP TYPE bt_page_items_type;
+ 
Index: contrib/pgstattuple/README.pgstattuple
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/contrib/pgstattuple/README.pgstattuple,v
retrieving revision 1.9
diff -c -r1.9 README.pgstattuple
*** contrib/pgstattuple/README.pgstattuple	2 Sep 2006 17:05:29 -0000	1.9
--- contrib/pgstattuple/README.pgstattuple	1 May 2007 11:29:24 -0000
***************
*** 56,108 ****
          avg_leaf_density   | 50.27
          leaf_fragmentation | 0
  
-     bt_metap
-     --------
-     bt_metap() returns information about the btree index metapage:
- 
-         test=> SELECT * FROM bt_metap('pg_cast_oid_index');
-         -[ RECORD 1 ]-----
-         magic     | 340322
-         version   | 2
-         root      | 1
-         level     | 0
-         fastroot  | 1
-         fastlevel | 0
- 
-     bt_page_stats
-     -------------
-     bt_page_stats() shows information about single btree pages:
- 
-         test=> SELECT * FROM bt_page_stats('pg_cast_oid_index', 1);
-         -[ RECORD 1 ]-+-----
-         blkno         | 1
-         type          | l
-         live_items    | 256
-         dead_items    | 0
-         avg_item_size | 12
-         page_size     | 8192
-         free_size     | 4056
-         btpo_prev     | 0
-         btpo_next     | 0
-         btpo          | 0
-         btpo_flags    | 3
- 
-     bt_page_items
-     -------------
-     bt_page_items() returns information about specific items on btree pages:
- 
-         test=> SELECT * FROM bt_page_items('pg_cast_oid_index', 1);
-          itemoffset |  ctid   | itemlen | nulls | vars |    data
-         ------------+---------+---------+-------+------+-------------
-                   1 | (0,1)   |      12 | f     | f    | 23 27 00 00
-                   2 | (0,2)   |      12 | f     | f    | 24 27 00 00
-                   3 | (0,3)   |      12 | f     | f    | 25 27 00 00
-                   4 | (0,4)   |      12 | f     | f    | 26 27 00 00
-                   5 | (0,5)   |      12 | f     | f    | 27 27 00 00
-                   6 | (0,6)   |      12 | f     | f    | 28 27 00 00
-                   7 | (0,7)   |      12 | f     | f    | 29 27 00 00
-                   8 | (0,8)   |      12 | f     | f    | 2a 27 00 00
- 
  
  2. Installing pgstattuple
  
--- 56,61 ----
***************
*** 140,145 ****
--- 93,102 ----
  
  5. History
  
+     2007/05/01
+ 
+ 	Moved page-level functions to pgforensics
+ 
      2006/06/28
  
  	Extended to work against indexes.
Index: contrib/pgstattuple/pgstatindex.c
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/contrib/pgstattuple/pgstatindex.c,v
retrieving revision 1.3
diff -c -r1.3 pgstatindex.c
*** contrib/pgstattuple/pgstatindex.c	16 Mar 2007 15:06:43 -0000	1.3
--- contrib/pgstattuple/pgstatindex.c	1 May 2007 09:24:11 -0000
***************
*** 36,65 ****
  #include "utils/inval.h"
  
  PG_FUNCTION_INFO_V1(pgstatindex);
- PG_FUNCTION_INFO_V1(bt_metap);
- PG_FUNCTION_INFO_V1(bt_page_items);
- PG_FUNCTION_INFO_V1(bt_page_stats);
  PG_FUNCTION_INFO_V1(pg_relpages);
  
  extern Datum pgstatindex(PG_FUNCTION_ARGS);
- extern Datum bt_metap(PG_FUNCTION_ARGS);
- extern Datum bt_page_items(PG_FUNCTION_ARGS);
- extern Datum bt_page_stats(PG_FUNCTION_ARGS);
  extern Datum pg_relpages(PG_FUNCTION_ARGS);
  
  #define PGSTATINDEX_TYPE "public.pgstatindex_type"
  #define PGSTATINDEX_NCOLUMNS 10
  
- #define BTMETAP_TYPE "public.bt_metap_type"
- #define BTMETAP_NCOLUMNS 6
- 
- #define BTPAGEITEMS_TYPE "public.bt_page_items_type"
- #define BTPAGEITEMS_NCOLUMNS 6
- 
- #define BTPAGESTATS_TYPE "public.bt_page_stats_type"
- #define BTPAGESTATS_NCOLUMNS 11
- 
- 
  #define IS_INDEX(r) ((r)->rd_rel->relkind == 'i')
  #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
  
--- 36,49 ----
***************
*** 73,222 ****
  			 elog(ERROR, "Block number out of range."); }
  
  /* ------------------------------------------------
-  * structure for single btree page statistics
-  * ------------------------------------------------
-  */
- typedef struct BTPageStat
- {
- 	uint32		blkno;
- 	uint32		live_items;
- 	uint32		dead_items;
- 	uint32		page_size;
- 	uint32		max_avail;
- 	uint32		free_size;
- 	uint32		avg_item_size;
- 	uint32		fragments;
- 	char		type;
- 
- 	/* opaque data */
- 	BlockNumber btpo_prev;
- 	BlockNumber btpo_next;
- 	union
- 	{
- 		uint32		level;
- 		TransactionId xact;
- 	}			btpo;
- 	uint16		btpo_flags;
- 	BTCycleId	btpo_cycleid;
- }	BTPageStat;
- 
- /* ------------------------------------------------
   * A structure for a whole btree index statistics
   * used by pgstatindex().
   * ------------------------------------------------
   */
  typedef struct BTIndexStat
  {
- 	uint32		magic;
  	uint32		version;
  	BlockNumber root_blkno;
  	uint32		level;
  
- 	BlockNumber fastroot;
- 	uint32		fastlevel;
- 
- 	uint32		live_items;
- 	uint32		dead_items;
- 
  	uint32		root_pages;
  	uint32		internal_pages;
  	uint32		leaf_pages;
  	uint32		empty_pages;
  	uint32		deleted_pages;
  
- 	uint32		page_size;
- 	uint32		avg_item_size;
- 
  	uint32		max_avail;
  	uint32		free_space;
  
  	uint32		fragments;
  }	BTIndexStat;
  
- /* -------------------------------------------------
-  * GetBTPageStatistics()
-  *
-  * Collect statistics of single b-tree leaf page
-  * -------------------------------------------------
-  */
- static void
- GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat * stat)
- {
- 	Page		page = BufferGetPage(buffer);
- 	PageHeader	phdr = (PageHeader) page;
- 	OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
- 	BTPageOpaque opaque = (BTPageOpaque) PageGetSpecialPointer(page);
- 	int			item_size = 0;
- 	int			off;
- 
- 	stat->blkno = blkno;
- 
- 	stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
- 
- 	stat->dead_items = stat->live_items = 0;
- 	stat->fragments = 0;
- 
- 	stat->page_size = PageGetPageSize(page);
- 
- 	/* page type (flags) */
- 	if (P_ISDELETED(opaque))
- 	{
- 		stat->type = 'd';
- 		stat->btpo.xact = opaque->btpo.xact;
- 		return;
- 	}
- 	else if (P_IGNORE(opaque))
- 		stat->type = 'e';
- 	else if (P_ISLEAF(opaque))
- 		stat->type = 'l';
- 	else if (P_ISROOT(opaque))
- 		stat->type = 'r';
- 	else
- 		stat->type = 'i';
- 
- 	/* btpage opaque data */
- 	stat->btpo_prev = opaque->btpo_prev;
- 	stat->btpo_next = opaque->btpo_next;
- 	stat->btpo.level = opaque->btpo.level;
- 	stat->btpo_flags = opaque->btpo_flags;
- 	stat->btpo_cycleid = opaque->btpo_cycleid;
- 
- 	/*----------------------------------------------
- 	 * If a next leaf is on the previous block,
- 	 * it means a fragmentation.
- 	 *----------------------------------------------
- 	 */
- 	if (stat->type == 'l')
- 	{
- 		if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno)
- 			stat->fragments++;
- 	}
- 
- 	/* count live and dead tuples, and free space */
- 	for (off = FirstOffsetNumber; off <= maxoff; off++)
- 	{
- 		IndexTuple	itup;
- 
- 		ItemId		id = PageGetItemId(page, off);
- 
- 		itup = (IndexTuple) PageGetItem(page, id);
- 
- 		item_size += IndexTupleSize(itup);
- 
- 		if (!ItemIdDeleted(id))
- 			stat->live_items++;
- 		else
- 			stat->dead_items++;
- 	}
- 	stat->free_size = PageGetFreeSpace(page);
- 
- 	if ((stat->live_items + stat->dead_items) > 0)
- 		stat->avg_item_size = item_size / (stat->live_items + stat->dead_items);
- 	else
- 		stat->avg_item_size = 0;
- }
- 
- 
  /* ------------------------------------------------------
   * pgstatindex()
   *
--- 57,84 ----
***************
*** 249,260 ****
  		Page		page = BufferGetPage(buffer);
  		BTMetaPageData *metad = BTPageGetMeta(page);
  
- 		indexStat.magic = metad->btm_magic;
  		indexStat.version = metad->btm_version;
  		indexStat.root_blkno = metad->btm_root;
  		indexStat.level = metad->btm_level;
- 		indexStat.fastroot = metad->btm_fastroot;
- 		indexStat.fastlevel = metad->btm_fastlevel;
  
  		ReleaseBuffer(buffer);
  	}
--- 111,119 ----
***************
*** 279,325 ****
  	 */
  	for (blkno = 1; blkno < nblocks; blkno++)
  	{
! 		Buffer		buffer = ReadBuffer(rel, blkno);
! 		BTPageStat	stat;
  
! 		/* scan one page */
! 		stat.blkno = blkno;
! 		GetBTPageStatistics(blkno, buffer, &stat);
! 
! 		/*---------------------
! 		 * page status (type)
! 		 *---------------------
! 		 */
! 		switch (stat.type)
! 		{
! 			case 'd':
! 				indexStat.deleted_pages++;
! 				break;
! 			case 'l':
! 				indexStat.leaf_pages++;
! 				break;
! 			case 'i':
! 				indexStat.internal_pages++;
! 				break;
! 			case 'e':
! 				indexStat.empty_pages++;
! 				break;
! 			case 'r':
! 				indexStat.root_pages++;
! 				break;
! 			default:
! 				elog(ERROR, "unknown page status.");
! 		}
  
! 		/* -- leaf fragmentation -- */
! 		indexStat.fragments += stat.fragments;
  
! 		if (stat.type == 'l')
  		{
! 			indexStat.max_avail += stat.max_avail;
! 			indexStat.free_space += stat.free_size;
  		}
  
  		ReleaseBuffer(buffer);
  	}
  
--- 138,186 ----
  	 */
  	for (blkno = 1; blkno < nblocks; blkno++)
  	{
! 		Buffer		buffer;
! 		Page		page;
! 		BTPageOpaque opaque;
! 
! 		/* Read and lock buffer */
! 		buffer = ReadBuffer(rel, blkno);
! 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
  
! 		page = BufferGetPage(buffer);
! 		opaque = (BTPageOpaque) PageGetSpecialPointer(page);
! 
! 		/* Determine page type, and update totals */
  
! 		if (P_ISDELETED(opaque))
! 			indexStat.deleted_pages++;
! 
! 		else if (P_IGNORE(opaque))
! 			indexStat.empty_pages++;
  
! 		else if (P_ISLEAF(opaque))
  		{
! 			int max_avail;
! 			max_avail = BLCKSZ - (BLCKSZ - ((PageHeader)page)->pd_special + SizeOfPageHeaderData);
! 			indexStat.max_avail += max_avail;
! 			indexStat.free_space += PageGetFreeSpace(page);
! 
! 			indexStat.leaf_pages++;
! 
! 			/*
! 			 * If the next leaf is on an earlier block, it
! 			 * means a fragmentation.
! 			 */
! 			if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno)
! 				indexStat.fragments++;
  		}
+ 		else if (P_ISROOT(opaque))
+ 			indexStat.root_pages++;
+ 
+ 		else
+ 			indexStat.internal_pages++;
  
+ 		/* Unlock and release buffer */
+ 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
  		ReleaseBuffer(buffer);
  	}
  
***************
*** 373,677 ****
  	PG_RETURN_DATUM(result);
  }
  
- /* -----------------------------------------------
-  * bt_page()
-  *
-  * Usage: SELECT * FROM bt_page('t1_pkey', 0);
-  * -----------------------------------------------
-  */
- Datum
- bt_page_stats(PG_FUNCTION_ARGS)
- {
- 	text	   *relname = PG_GETARG_TEXT_P(0);
- 	uint32		blkno = PG_GETARG_UINT32(1);
- 	Buffer		buffer;
- 
- 	Relation	rel;
- 	RangeVar   *relrv;
- 	Datum		result;
- 
- 	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
- 	rel = relation_openrv(relrv, AccessShareLock);
- 
- 	CHECK_RELATION_BLOCK_RANGE(rel, blkno);
- 
- 	buffer = ReadBuffer(rel, blkno);
- 
- 	if (!IS_INDEX(rel) || !IS_BTREE(rel))
- 		elog(ERROR, "bt_page_stats() can be used only on b-tree index.");
- 
- 	if (blkno == 0)
- 		elog(ERROR, "Block 0 is a meta page.");
- 
- 	{
- 		HeapTuple	tuple;
- 		TupleDesc	tupleDesc;
- 		int			j;
- 		char	   *values[BTPAGESTATS_NCOLUMNS];
- 
- 		BTPageStat	stat;
- 
- 		GetBTPageStatistics(blkno, buffer, &stat);
- 
- 		tupleDesc = RelationNameGetTupleDesc(BTPAGESTATS_TYPE);
- 
- 		j = 0;
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", stat.blkno);
- 
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%c", stat.type);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", stat.live_items);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", stat.dead_items);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", stat.avg_item_size);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", stat.page_size);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", stat.free_size);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", stat.btpo_prev);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", stat.btpo_next);
- 
- 		values[j] = palloc(32);
- 		if (stat.type == 'd')
- 			snprintf(values[j++], 32, "%d", stat.btpo.xact);
- 		else
- 			snprintf(values[j++], 32, "%d", stat.btpo.level);
- 
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", stat.btpo_flags);
- 
- 		tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
- 									   values);
- 
- 		result = TupleGetDatum(TupleDescGetSlot(tupleDesc), tuple);
- 	}
- 
- 	ReleaseBuffer(buffer);
- 
- 	relation_close(rel, AccessShareLock);
- 
- 	PG_RETURN_DATUM(result);
- }
- 
- /*-------------------------------------------------------
-  * bt_page_items()
-  *
-  * Get IndexTupleData set in a leaf page
-  *
-  * Usage: SELECT * FROM bt_page_items('t1_pkey', 0);
-  *-------------------------------------------------------
-  */
- /* ---------------------------------------------------
-  * data structure for SRF to hold a scan information
-  * ---------------------------------------------------
-  */
- struct user_args
- {
- 	TupleDesc	tupd;
- 	Relation	rel;
- 	Buffer		buffer;
- 	Page		page;
- 	uint16		offset;
- };
- 
- Datum
- bt_page_items(PG_FUNCTION_ARGS)
- {
- 	text	   *relname = PG_GETARG_TEXT_P(0);
- 	uint32		blkno = PG_GETARG_UINT32(1);
- 
- 	RangeVar   *relrv;
- 	Datum		result;
- 	char	   *values[BTPAGEITEMS_NCOLUMNS];
- 	BTPageOpaque opaque;
- 	HeapTuple	tuple;
- 	ItemId		id;
- 
- 	FuncCallContext *fctx;
- 	MemoryContext mctx;
- 	struct user_args *uargs = NULL;
- 
- 	if (blkno == 0)
- 		elog(ERROR, "Block 0 is a meta page.");
- 
- 	if (SRF_IS_FIRSTCALL())
- 	{
- 		fctx = SRF_FIRSTCALL_INIT();
- 		mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
- 
- 		uargs = palloc(sizeof(struct user_args));
- 
- 		uargs->tupd = RelationNameGetTupleDesc(BTPAGEITEMS_TYPE);
- 		uargs->offset = FirstOffsetNumber;
- 
- 		relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
- 		uargs->rel = relation_openrv(relrv, AccessShareLock);
- 
- 		CHECK_RELATION_BLOCK_RANGE(uargs->rel, blkno);
- 
- 		uargs->buffer = ReadBuffer(uargs->rel, blkno);
- 
- 		if (!IS_INDEX(uargs->rel) || !IS_BTREE(uargs->rel))
- 			elog(ERROR, "bt_page_items() can be used only on b-tree index.");
- 
- 		uargs->page = BufferGetPage(uargs->buffer);
- 
- 		opaque = (BTPageOpaque) PageGetSpecialPointer(uargs->page);
- 
- 		if (P_ISDELETED(opaque))
- 			elog(NOTICE, "bt_page_items(): this page is deleted.");
- 
- 		fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
- 		fctx->user_fctx = uargs;
- 
- 		MemoryContextSwitchTo(mctx);
- 	}
- 
- 	fctx = SRF_PERCALL_SETUP();
- 	uargs = fctx->user_fctx;
- 
- 	if (fctx->call_cntr < fctx->max_calls)
- 	{
- 		IndexTuple	itup;
- 
- 		id = PageGetItemId(uargs->page, uargs->offset);
- 
- 		if (!ItemIdIsValid(id))
- 			elog(ERROR, "Invalid ItemId.");
- 
- 		itup = (IndexTuple) PageGetItem(uargs->page, id);
- 
- 		{
- 			int			j = 0;
- 
- 			BlockNumber blkno = BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid));
- 
- 			values[j] = palloc(32);
- 			snprintf(values[j++], 32, "%d", uargs->offset);
- 			values[j] = palloc(32);
- 			snprintf(values[j++], 32, "(%u,%u)", blkno, itup->t_tid.ip_posid);
- 			values[j] = palloc(32);
- 			snprintf(values[j++], 32, "%d", (int) IndexTupleSize(itup));
- 			values[j] = palloc(32);
- 			snprintf(values[j++], 32, "%c", IndexTupleHasNulls(itup) ? 't' : 'f');
- 			values[j] = palloc(32);
- 			snprintf(values[j++], 32, "%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
- 
- 			{
- 				int			off;
- 				char	   *dump;
- 				char	   *ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
- 
- 				dump = palloc(IndexTupleSize(itup) * 3);
- 				memset(dump, 0, IndexTupleSize(itup) * 3);
- 
- 				for (off = 0;
- 					 off < IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
- 					 off++)
- 				{
- 					if (dump[0] == '\0')
- 						sprintf(dump, "%02x", *(ptr + off) & 0xff);
- 					else
- 					{
- 						char		buf[4];
- 
- 						sprintf(buf, " %02x", *(ptr + off) & 0xff);
- 						strcat(dump, buf);
- 					}
- 				}
- 				values[j] = dump;
- 			}
- 
- 			tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(uargs->tupd), values);
- 			result = TupleGetDatum(TupleDescGetSlot(uargs->tupd), tuple);
- 		}
- 
- 		uargs->offset = uargs->offset + 1;
- 
- 		SRF_RETURN_NEXT(fctx, result);
- 	}
- 	else
- 	{
- 		ReleaseBuffer(uargs->buffer);
- 		relation_close(uargs->rel, AccessShareLock);
- 
- 		SRF_RETURN_DONE(fctx);
- 	}
- }
- 
- 
- /* ------------------------------------------------
-  * bt_metap()
-  *
-  * Get a btree meta-page information
-  *
-  * Usage: SELECT * FROM bt_metap('t1_pkey')
-  * ------------------------------------------------
-  */
- Datum
- bt_metap(PG_FUNCTION_ARGS)
- {
- 	text	   *relname = PG_GETARG_TEXT_P(0);
- 	Buffer		buffer;
- 
- 	Relation	rel;
- 	RangeVar   *relrv;
- 	Datum		result;
- 
- 	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
- 	rel = relation_openrv(relrv, AccessShareLock);
- 
- 	if (!IS_INDEX(rel) || !IS_BTREE(rel))
- 		elog(ERROR, "bt_metap() can be used only on b-tree index.");
- 
- 	buffer = ReadBuffer(rel, 0);
- 
- 	{
- 		BTMetaPageData *metad;
- 
- 		TupleDesc	tupleDesc;
- 		int			j;
- 		char	   *values[BTMETAP_NCOLUMNS];
- 		HeapTuple	tuple;
- 
- 		Page		page = BufferGetPage(buffer);
- 
- 		metad = BTPageGetMeta(page);
- 
- 		tupleDesc = RelationNameGetTupleDesc(BTMETAP_TYPE);
- 
- 		j = 0;
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", metad->btm_magic);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", metad->btm_version);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", metad->btm_root);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", metad->btm_level);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", metad->btm_fastroot);
- 		values[j] = palloc(32);
- 		snprintf(values[j++], 32, "%d", metad->btm_fastlevel);
- 
- 		tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
- 									   values);
- 
- 		result = TupleGetDatum(TupleDescGetSlot(tupleDesc), tuple);
- 	}
- 
- 	ReleaseBuffer(buffer);
- 
- 	relation_close(rel, AccessShareLock);
- 
- 	PG_RETURN_DATUM(result);
- }
- 
  /* --------------------------------------------------------
   * pg_relpages()
   *
--- 234,239 ----
Index: contrib/pgstattuple/pgstattuple.sql.in
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/contrib/pgstattuple/pgstattuple.sql.in,v
retrieving revision 1.12
diff -c -r1.12 pgstattuple.sql.in
*** contrib/pgstattuple/pgstattuple.sql.in	4 Sep 2006 02:03:04 -0000	1.12
--- contrib/pgstattuple/pgstattuple.sql.in	1 May 2007 09:25:28 -0000
***************
*** 45,106 ****
  LANGUAGE 'C' STRICT;
  
  --
- -- bt_metap()
- --
- CREATE TYPE bt_metap_type AS (
-   magic int4,
-   version int4,
-   root int4,
-   level int4,
-   fastroot int4,
-   fastlevel int4
- );
- 
- CREATE OR REPLACE FUNCTION bt_metap(text)
- RETURNS bt_metap_type
- AS 'MODULE_PATHNAME', 'bt_metap'
- LANGUAGE 'C' STRICT;
- 
- --
- -- bt_page_stats()
- --
- CREATE TYPE bt_page_stats_type AS (
-   blkno int4,
-   type char,
-   live_items int4,
-   dead_items int4,
-   avg_item_size float,
-   page_size int4,
-   free_size int4,
-   btpo_prev int4,
-   btpo_next int4,
-   btpo int4,
-   btpo_flags int4
- );
- 
- CREATE OR REPLACE FUNCTION bt_page_stats(text, int4)
- RETURNS bt_page_stats_type
- AS 'MODULE_PATHNAME', 'bt_page_stats'
- LANGUAGE 'C' STRICT;
- 
- --
- -- bt_page_items()
- --
- CREATE TYPE bt_page_items_type AS (
-   itemoffset int4,
-   ctid tid,
-   itemlen int4,
-   nulls bool,
-   vars bool,
-   data text
- );
- 
- CREATE OR REPLACE FUNCTION bt_page_items(text, int4)
- RETURNS SETOF bt_page_items_type
- AS 'MODULE_PATHNAME', 'bt_page_items'
- LANGUAGE 'C' STRICT;
- 
- --
  -- pg_relpages()
  --
  CREATE OR REPLACE FUNCTION pg_relpages(text)
--- 45,50 ----
Index: contrib/pgstattuple/uninstall_pgstattuple.sql
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/contrib/pgstattuple/uninstall_pgstattuple.sql,v
retrieving revision 1.2
diff -c -r1.2 uninstall_pgstattuple.sql
*** contrib/pgstattuple/uninstall_pgstattuple.sql	4 Sep 2006 02:03:04 -0000	1.2
--- contrib/pgstattuple/uninstall_pgstattuple.sql	1 May 2007 13:18:52 -0000
***************
*** 8,20 ****
  DROP FUNCTION pgstatindex(text);
  DROP TYPE pgstatindex_type;
  
- DROP FUNCTION bt_metap(text);
- DROP TYPE bt_metap_type;
- 
- DROP FUNCTION bt_page_stats(text, int4);
- DROP TYPE bt_page_stats_type;
- 
- DROP FUNCTION bt_page_items(text, int4);
- DROP TYPE bt_page_items_type;
- 
  DROP FUNCTION pg_relpages(text);
--- 8,11 ----
---------------------------(end of broadcast)---------------------------
TIP 2: Don't 'kill -9' the postmaster

Reply via email to