В письме от 11 сентября 2015 15:12:04 пользователь Michael Paquier написал:

> > Ok.Let's come to the final decision with tuple_data_parse, and i will add
> > this switch there and to pure sql heap_page_item_attrs
> 
> Fine for me.

Here is final version with documentation.

Hope it will be the last one. :-)

-- 
Nikolay Shaplov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index aec5258..e4bc1af 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -5,7 +5,7 @@ OBJS		= rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o \
 		  brinfuncs.o ginfuncs.o $(WIN32RES)
 
 EXTENSION = pageinspect
-DATA = pageinspect--1.3.sql pageinspect--1.2--1.3.sql \
+DATA = pageinspect--1.4.sql pageinspect--1.3--1.4.sql pageinspect--1.2--1.3.sql \
 	pageinspect--1.1--1.2.sql pageinspect--1.0--1.1.sql \
 	pageinspect--unpackaged--1.0.sql
 PGFILEDESC = "pageinspect - functions to inspect contents of database pages"
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 8d1666c..d42ca48 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -29,7 +29,11 @@
 #include "funcapi.h"
 #include "utils/builtins.h"
 #include "miscadmin.h"
+#include "utils/array.h"
+#include "utils/rel.h"
+#include "catalog/pg_type.h"
 
+Datum split_tuple_data(char *tuple_data, uint16 tuple_data_len, TupleDesc tuple_desc, uint16 t_infomask, uint16 t_infomask2, bits8 *t_bits, bool do_detoast, int error_level);
 
 /*
  * bits_to_text
@@ -122,8 +126,8 @@ heap_page_items(PG_FUNCTION_ARGS)
 		HeapTuple	resultTuple;
 		Datum		result;
 		ItemId		id;
-		Datum		values[13];
-		bool		nulls[13];
+		Datum		values[14];
+		bool		nulls[14];
 		uint16		lp_offset;
 		uint16		lp_flags;
 		uint16		lp_len;
@@ -155,6 +159,8 @@ heap_page_items(PG_FUNCTION_ARGS)
 		{
 			HeapTupleHeader tuphdr;
 			int			bits_len;
+			bytea		*tuple_data_bytea;
+			int			tuple_data_len;
 
 			/* Extract information from the tuple header */
 
@@ -168,6 +174,13 @@ heap_page_items(PG_FUNCTION_ARGS)
 			values[9] = UInt32GetDatum(tuphdr->t_infomask);
 			values[10] = UInt8GetDatum(tuphdr->t_hoff);
 
+			/* Copy raw tuple data into bytea attribute */
+			tuple_data_len = lp_len - tuphdr->t_hoff;
+			tuple_data_bytea = (bytea *) palloc(tuple_data_len + VARHDRSZ);
+			SET_VARSIZE(tuple_data_bytea, tuple_data_len + VARHDRSZ);
+			memcpy(VARDATA(tuple_data_bytea), (char *) tuphdr + tuphdr->t_hoff, tuple_data_len);
+			values[13] = PointerGetDatum(tuple_data_bytea);
+			nulls[13] = false;
 			/*
 			 * We already checked that the item is completely within the raw
 			 * page passed to us, with the length given in the line pointer.
@@ -208,7 +221,7 @@ heap_page_items(PG_FUNCTION_ARGS)
 			 */
 			int			i;
 
-			for (i = 4; i <= 12; i++)
+			for (i = 4; i <= 13; i++)
 				nulls[i] = true;
 		}
 
@@ -223,3 +236,210 @@ heap_page_items(PG_FUNCTION_ARGS)
 	else
 		SRF_RETURN_DONE(fctx);
 }
+
+PG_FUNCTION_INFO_V1(tuple_data_split);
+Datum
+tuple_data_split(PG_FUNCTION_ARGS)
+{
+	Oid				rel_oid;
+	bytea			*raw_data;
+	uint16			t_infomask;
+	uint16			t_infomask2;
+	text			*t_bits_str;
+	RelationData	*rel;
+	TupleDesc		tuple_desc;
+	bool			do_detoast = false;
+	int				error_level = ERROR;
+
+	bits8			*t_bits = NULL;
+	Datum			res;
+
+	rel_oid		= PG_GETARG_OID(0);
+	raw_data	= PG_GETARG_BYTEA_P(1);
+	t_infomask	= PG_GETARG_INT16(2);
+	t_infomask2	= PG_GETARG_INT16(3);
+	t_bits_str	= PG_ARGISNULL(4) ? NULL : PG_GETARG_TEXT_P(4);
+	if (PG_NARGS()>=6) do_detoast = PG_GETARG_BOOL(5);
+	if (PG_NARGS()>=7) error_level = PG_GETARG_BOOL(6)?WARNING:ERROR;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					(errmsg("must be superuser to use raw page functions"))));
+
+	/*
+	 * Here we converting t_bits string back to the bits8 array,
+	 * as it really is in the tuple header
+	 */
+	if (t_infomask & HEAP_HASNULL)
+	{
+		int bits_str_len;
+		int bits_len;
+		char * p;
+		int off;
+		char byte = 0;
+		bits_len = (t_infomask2 & HEAP_NATTS_MASK) / 8 + 1;
+		if (! t_bits_str)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+						errmsg("t_bits argument is NULL, though we expect it to be NOT NULL and %i character long", bits_len * 8)));
+		}
+		bits_str_len =  VARSIZE(t_bits_str) - VARHDRSZ;
+		if (bits_str_len % 8)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+						errmsg("t_bits argument length should be multiple of eight")));
+		}
+		if (bits_len * 8 != bits_str_len)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+						errmsg("wrong t_bits argument length. Expected %i, actual is %i", bits_len * 8, bits_str_len)));
+		}
+		t_bits = palloc(bits_len + 1);
+
+		p = (char *) t_bits_str + VARHDRSZ;
+		off = 0;
+		while (off<bits_str_len)
+		{
+			if (!(off % 8))
+			{
+				byte = 0;
+			}
+			if (( p[off] == '0') || (p[off] == '1'))
+			{
+				byte = byte | ( (p[off]-'0')<<off % 8);
+			} else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_DATA_CORRUPTED),
+							errmsg("illegal character '%c' in t_bits string", p[off])));
+			}
+			if (off % 8 == 7)
+			{
+				t_bits[off / 8] = byte;
+			}
+			off++;
+		}
+	} else
+	{
+		if (t_bits_str)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+						errmsg("t_bits string is expected to be NULL, but instead it is %i bytes length", VARSIZE(t_bits_str) - VARHDRSZ)));
+		}
+	}
+	rel = relation_open(rel_oid, NoLock);
+	tuple_desc = CreateTupleDescCopyConstr(rel->rd_att);
+	relation_close(rel, NoLock);
+	res = split_tuple_data((char *) raw_data + VARHDRSZ, VARSIZE(raw_data) - VARHDRSZ, tuple_desc, t_infomask, t_infomask2, t_bits, do_detoast, error_level);
+	PG_RETURN_ARRAYTYPE_P(res);
+}
+
+
+Datum
+split_tuple_data(char *tuple_data, uint16 tuple_data_len, TupleDesc tuple_desc, uint16 t_infomask, uint16 t_infomask2, bits8 *t_bits, bool do_detoast, int error_level)
+{
+	ArrayBuildState *raw_attrs;
+	int nattrs;
+	int i;
+	int off;
+
+	/*
+	 * Here we reimplement the basic functionality of nocachegetattr from
+	 * backend/access/common/heaptuple.c whitch is basically used for fetching
+	 * attributes from tuple when it is not cached. We can not use nocachegetattr here
+	 * directly, because we should ignore all cache optimisations and other stuff
+	 * just get binary data as it is.
+	 */
+
+	raw_attrs = initArrayResult(BYTEAOID,CurrentMemoryContext,0);
+	off = 0;
+	nattrs = tuple_desc->natts;
+	if (error_level &&
+			nattrs < (t_infomask2 & HEAP_NATTS_MASK))
+	{
+		ereport(error_level,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+					errmsg("data corruption: number of attributes in tuple header is greater than number of attributes in tuple descripor")));
+	}
+
+	for(i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute attr;
+		Datum raw_attr = PointerGetDatum(NULL);
+		bool is_null;
+
+		attr = tuple_desc->attrs[i];
+		is_null = (t_infomask & HEAP_HASNULL) && att_isnull(i, t_bits);
+		/*
+		 * Tuple header can specify less attributes then tuple descriptor
+		 * as ALTER TABLE ADD COLUMN without DEFAULT keyword does not
+		 * actualy change tuples in pages, so attributes with numbers greater
+		 * than t_infomask2 & HEAP_NATTS_MASK should be treated as NULL
+		 */
+		if (i >= (t_infomask2 & HEAP_NATTS_MASK) )
+			is_null = true;
+		if (!is_null)
+		{
+			int len;
+			bytea * attr_data;
+			if (attr->attlen == -1)
+			{
+				off = att_align_pointer(off, tuple_desc->attrs[i]->attalign, -1, tuple_data + off);
+				/*
+				 * As VARSIZE_ANY throws an exeption if it can't properly detect
+				 * type of external storage in macros VARTAG_SIZE, so we repeat
+				 * this check here to preform nicer error handling
+				 */
+				if ( error_level &&
+					VARATT_IS_1B_E(tuple_data + off) &&
+					VARTAG_EXTERNAL(tuple_data + off) != VARTAG_INDIRECT &&
+					VARTAG_EXTERNAL(tuple_data + off) != VARTAG_ONDISK)
+				{
+					ereport(error_level,
+						(errcode(ERRCODE_DATA_CORRUPTED),
+							errmsg("data corruption: First byte of varlen attr seems to be corrupted")));
+				}
+				len = VARSIZE_ANY(tuple_data + off);
+			}
+			else
+			{
+				off = att_align_nominal(off, tuple_desc->attrs[i]->attalign);
+				len = attr->attlen;
+			}
+			if ( error_level &&
+					tuple_data_len < off + len)
+			{
+				ereport(error_level,
+						(errcode(ERRCODE_DATA_CORRUPTED),
+							errmsg("data corruption: Iterating over tuple data reached out of actual tuple size")));
+			}
+			attr_data = (bytea *) palloc(len + VARHDRSZ);
+			SET_VARSIZE(attr_data, len + VARHDRSZ);
+			memcpy(VARDATA(attr_data), tuple_data + off, len);
+			raw_attr = PointerGetDatum(attr_data);
+
+			if ( attr->attlen == -1 && do_detoast)
+			{
+				Datum raw_attr_copy;
+				raw_attr_copy = PointerGetDatum(PG_DETOAST_DATUM_COPY(tuple_data + off));
+				pfree(attr_data);
+				raw_attr = raw_attr_copy;
+			}
+			off = att_addlength_pointer(off, tuple_desc->attrs[i]->attlen, tuple_data + off);
+		}
+		raw_attrs = accumArrayResult(raw_attrs, raw_attr, is_null, BYTEAOID, CurrentMemoryContext);
+	}
+	if (error_level &&
+			tuple_data_len != off)
+	{
+		ereport(error_level,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+					errmsg("data corruption: Iterating over tuple data did not actualy reach tuple end")));
+	}
+	return makeArrayResult(raw_attrs, CurrentMemoryContext);
+}
diff --git a/contrib/pageinspect/pageinspect--1.3--1.4.sql b/contrib/pageinspect/pageinspect--1.3--1.4.sql
new file mode 100644
index 0000000..5a16261
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.3--1.4.sql
@@ -0,0 +1,87 @@
+/* contrib/pageinspect/pageinspect--1.3--1.4.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.4'" to load this file. \quit
+
+--
+-- heap_page_items()
+--
+DROP FUNCTION heap_page_items(bytea);
+CREATE FUNCTION heap_page_items(IN page bytea,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_oid oid,
+    OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(t_oid rel_oid, tuple_data bytea, t_infomask integer, t_infomask2 integer, t_bits text) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+
+CREATE FUNCTION tuple_data_split(t_oid rel_oid, tuple_data bytea, t_infomask integer, t_infomask2 integer, t_bits text, do_detoast bool) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+
+CREATE FUNCTION tuple_data_split(t_oid rel_oid, tuple_data bytea, t_infomask integer, t_infomask2 integer, t_bits text, do_detoast bool, warning_mode bool) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass, IN do_detoast bool,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_oid oid,
+    OUT t_attrs bytea[]
+    )
+RETURNS SETOF record
+AS 'SELECT lp, lp_off, lp_flags, lp_len, t_xmin, t_xmax, t_field3, t_ctid, t_infomask2, t_infomask, t_hoff, t_bits, t_oid, tuple_data_split(rel_oid, t_data, t_infomask, t_infomask2, t_bits, do_detoast) as t_attrs from heap_page_items(page)'
+LANGUAGE SQL;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN heap_oid regclass,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_oid oid,
+    OUT t_attrs bytea[]
+    )
+RETURNS SETOF record
+AS 'SELECT * from heap_page_item_attrs(page, heap_oid, false)'
+LANGUAGE SQL;
diff --git a/contrib/pageinspect/pageinspect--1.3.sql b/contrib/pageinspect/pageinspect--1.3.sql
deleted file mode 100644
index a99e058..0000000
--- a/contrib/pageinspect/pageinspect--1.3.sql
+++ /dev/null
@@ -1,189 +0,0 @@
-/* contrib/pageinspect/pageinspect--1.3.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
-
---
--- get_raw_page()
---
-CREATE FUNCTION get_raw_page(text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page'
-LANGUAGE C STRICT;
-
-CREATE FUNCTION get_raw_page(text, text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page_fork'
-LANGUAGE C STRICT;
-
---
--- page_header()
---
-CREATE FUNCTION page_header(IN page bytea,
-    OUT lsn pg_lsn,
-    OUT checksum smallint,
-    OUT flags smallint,
-    OUT lower smallint,
-    OUT upper smallint,
-    OUT special smallint,
-    OUT pagesize smallint,
-    OUT version smallint,
-    OUT prune_xid xid)
-AS 'MODULE_PATHNAME', 'page_header'
-LANGUAGE C STRICT;
-
---
--- heap_page_items()
---
-CREATE FUNCTION heap_page_items(IN page bytea,
-    OUT lp smallint,
-    OUT lp_off smallint,
-    OUT lp_flags smallint,
-    OUT lp_len smallint,
-    OUT t_xmin xid,
-    OUT t_xmax xid,
-    OUT t_field3 int4,
-    OUT t_ctid tid,
-    OUT t_infomask2 integer,
-    OUT t_infomask integer,
-    OUT t_hoff smallint,
-    OUT t_bits text,
-    OUT t_oid oid)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'heap_page_items'
-LANGUAGE C STRICT;
-
---
--- bt_metap()
---
-CREATE FUNCTION bt_metap(IN relname text,
-    OUT magic int4,
-    OUT version int4,
-    OUT root int4,
-    OUT level int4,
-    OUT fastroot int4,
-    OUT fastlevel int4)
-AS 'MODULE_PATHNAME', 'bt_metap'
-LANGUAGE C STRICT;
-
---
--- bt_page_stats()
---
-CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
-    OUT blkno int4,
-    OUT type "char",
-    OUT live_items int4,
-    OUT dead_items int4,
-    OUT avg_item_size int4,
-    OUT page_size int4,
-    OUT free_size int4,
-    OUT btpo_prev int4,
-    OUT btpo_next int4,
-    OUT btpo int4,
-    OUT btpo_flags int4)
-AS 'MODULE_PATHNAME', 'bt_page_stats'
-LANGUAGE C STRICT;
-
---
--- bt_page_items()
---
-CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
-    OUT itemoffset smallint,
-    OUT ctid tid,
-    OUT itemlen smallint,
-    OUT nulls bool,
-    OUT vars bool,
-    OUT data text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'bt_page_items'
-LANGUAGE C STRICT;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
-	OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
-	OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
-	OUT itemoffset int,
-	OUT blknum int,
-	OUT attnum int,
-	OUT allnulls bool,
-	OUT hasnulls bool,
-	OUT placeholder bool,
-	OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_page_items'
-LANGUAGE C STRICT;
-
---
--- fsm_page_contents()
---
-CREATE FUNCTION fsm_page_contents(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'fsm_page_contents'
-LANGUAGE C STRICT;
-
---
--- GIN functions
---
-
---
--- gin_metapage_info()
---
-CREATE FUNCTION gin_metapage_info(IN page bytea,
-    OUT pending_head bigint,
-    OUT pending_tail bigint,
-    OUT tail_free_size int4,
-    OUT n_pending_pages bigint,
-    OUT n_pending_tuples bigint,
-    OUT n_total_pages bigint,
-    OUT n_entry_pages bigint,
-    OUT n_data_pages bigint,
-    OUT n_entries bigint,
-    OUT version int4)
-AS 'MODULE_PATHNAME', 'gin_metapage_info'
-LANGUAGE C STRICT;
-
---
--- gin_page_opaque_info()
---
-CREATE FUNCTION gin_page_opaque_info(IN page bytea,
-    OUT rightlink bigint,
-    OUT maxoff int4,
-    OUT flags text[])
-AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
-LANGUAGE C STRICT;
-
---
--- gin_leafpage_items()
---
-CREATE FUNCTION gin_leafpage_items(IN page bytea,
-    OUT first_tid tid,
-    OUT nbytes int2,
-    OUT tids tid[])
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'gin_leafpage_items'
-LANGUAGE C STRICT;
diff --git a/contrib/pageinspect/pageinspect--1.4.sql b/contrib/pageinspect/pageinspect--1.4.sql
new file mode 100644
index 0000000..c09657b
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.4.sql
@@ -0,0 +1,251 @@
+/* contrib/pageinspect/pageinspect--1.4.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+    OUT lsn pg_lsn,
+    OUT checksum smallint,
+    OUT flags smallint,
+    OUT lower smallint,
+    OUT upper smallint,
+    OUT special smallint,
+    OUT pagesize smallint,
+    OUT version smallint,
+    OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_oid oid,
+    OUT t_data bytea
+    )
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid, tuple_data bytea, t_infomask integer, t_infomask2 integer, t_bits text) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+
+CREATE FUNCTION tuple_data_split(rel_oid oid, tuple_data bytea, t_infomask integer, t_infomask2 integer, t_bits text, do_detoast bool) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+
+CREATE FUNCTION tuple_data_split(rel_oid oid, tuple_data bytea, t_infomask integer, t_infomask2 integer, t_bits text, do_detoast bool, warning_mode bool) RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass, IN do_detoast bool,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_oid oid,
+    OUT t_attrs bytea[]
+    )
+RETURNS SETOF record
+AS 'SELECT lp, lp_off, lp_flags, lp_len, t_xmin, t_xmax, t_field3, t_ctid, t_infomask2, t_infomask, t_hoff, t_bits, t_oid, tuple_data_split(rel_oid, t_data, t_infomask, t_infomask2, t_bits, do_detoast) as t_attrs from heap_page_items(page)'
+LANGUAGE SQL;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN heap_oid regclass,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_oid oid,
+    OUT t_attrs bytea[]
+    )
+RETURNS SETOF record
+AS 'SELECT * from heap_page_item_attrs(page, heap_oid, false)'
+LANGUAGE SQL;
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+    OUT magic int4,
+    OUT version int4,
+    OUT root int4,
+    OUT level int4,
+    OUT fastroot int4,
+    OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+    OUT blkno int4,
+    OUT type "char",
+    OUT live_items int4,
+    OUT dead_items int4,
+    OUT avg_item_size int4,
+    OUT page_size int4,
+    OUT free_size int4,
+    OUT btpo_prev int4,
+    OUT btpo_next int4,
+    OUT btpo int4,
+    OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+    OUT itemoffset smallint,
+    OUT ctid tid,
+    OUT itemlen smallint,
+    OUT nulls bool,
+    OUT vars bool,
+    OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT;
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+	OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+	OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+	OUT itemoffset int,
+	OUT blknum int,
+	OUT attnum int,
+	OUT allnulls bool,
+	OUT hasnulls bool,
+	OUT placeholder bool,
+	OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+    OUT pending_head bigint,
+    OUT pending_tail bigint,
+    OUT tail_free_size int4,
+    OUT n_pending_pages bigint,
+    OUT n_pending_tuples bigint,
+    OUT n_total_pages bigint,
+    OUT n_entry_pages bigint,
+    OUT n_data_pages bigint,
+    OUT n_entries bigint,
+    OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+    OUT rightlink bigint,
+    OUT maxoff int4,
+    OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+    OUT first_tid tid,
+    OUT nbytes int2,
+    OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index a9dab33..68c7d61 100644
--- a/contrib/pageinspect/pageinspect.control
+++ b/contrib/pageinspect/pageinspect.control
@@ -1,5 +1,5 @@
 # pageinspect extension
 comment = 'inspect the contents of database pages at a low level'
-default_version = '1.3'
+default_version = '1.4'
 module_pathname = '$libdir/pageinspect'
 relocatable = true
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index b95cc81..f877390 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -93,19 +93,355 @@ test=# SELECT * FROM page_header(get_raw_page('pg_class', 0));
     <listitem>
      <para>
       <function>heap_page_items</function> 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.
+      page.  For those line pointers that are in use,
+      <function>heap_page_items</function> also shows tuple headers and raw
+      tuple data. All tuples are shown, whether or not the tuples were visible
+      to an MVCC snapshot at the time the raw page was copied.
      </para>
      <para>
       A heap page image obtained with <function>get_raw_page</function> should
       be passed as argument.  For example:
 <screen>
-test=# SELECT * FROM heap_page_items(get_raw_page('pg_class', 0));
+
+test=# select * from heap_page_items(get_raw_page('pg_range', 0));
+ lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |                       t_data
+----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+----------------------------------------------------
+  1 |   8144 |        1 |     48 |      1 |      0 |        0 | (0,1)  |           6 |       2304 |     24 |        |       | \x400f00001700000000000000ba0700004a0f0000520f0000
+  2 |   8096 |        1 |     48 |      1 |      0 |        0 | (0,2)  |           6 |       2304 |     24 |        |       | \x420f0000a406000000000000350c000000000000540f0000
+  3 |   8048 |        1 |     48 |      1 |      0 |        0 | (0,3)  |           6 |       2304 |     24 |        |       | \x440f00005a04000000000000380c000000000000590f0000
+  4 |   8000 |        1 |     48 |      1 |      0 |        0 | (0,4)  |           6 |       2304 |     24 |        |       | \x460f0000a004000000000000370c0000000000005a0f0000
+  5 |   7952 |        1 |     48 |      1 |      0 |        0 | (0,5)  |           6 |       2304 |     24 |        |       | \x480f00003a04000000000000320c00004b0f0000550f0000
+  6 |   7904 |        1 |     48 |      1 |      0 |        0 | (0,6)  |           6 |       2304 |     24 |        |       | \x560f00001400000000000000340c0000580f0000530f0000
+(6 rows)
+
+</screen>
+      The output columns are:
+
+     <informaltable>
+     <tgroup cols="3">
+      <thead>
+       <row>
+        <entry>Column</entry>
+        <entry>Type</entry>
+        <entry>Description</entry>
+       </row>
+      </thead>
+
+      <tbody>
+       <row>
+        <entry><structfield>lp</structfield></entry>
+        <entry><type>smallint</type></entry>
+        <entry>line number</entry>
+       </row>
+
+       <row>
+        <entry><structfield>lp_off</structfield></entry>
+        <entry><type>smallint</type></entry>
+        <entry>offset to tuple (from start of page)</entry>
+       </row>
+
+       <row>
+        <entry><structfield>lp_flags</structfield></entry>
+        <entry><type>smallint</type></entry>
+        <entry>state of item pointer:
+<screen>
+0 - unused (should always have lp_len=0);
+1 - used (should always have lp_len>0);
+2 - HOT redirect (should have lp_len=0);
+3 - dead, may or may not have storage.
+</screen>
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>lp_len</structfield></entry>
+        <entry><type>smallint</type></entry>
+        <entry>length of tuple</entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_xmin</structfield></entry>
+        <entry><type>xid</type></entry>
+        <entry>transaction ID when this tuple was created. See
+         <xref linkend="ddl-system-columns">
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_xmax</structfield></entry>
+        <entry><type>xid</type></entry>
+        <entry>transaction ID when tuple was deleted or replaced with newer
+         version. See <xref linkend="ddl-system-columns">
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_field3</structfield></entry>
+        <entry><type>int4</type></entry>
+        <entry><filename>t_field3</filename> is a tricky a attribute, that can
+         be treated as Cmin, Cmax (command numbers inside transaction),
+         combination of Cmin and Cmax, or as Xvac - that is used by
+         oldstyle VACUUM. See <xref linkend="storage-page-layout">
+         and <xref linkend="ddl-system-columns"> for more
+         info
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_ctid</structfield></entry>
+        <entry><type>tid</type></entry>
+        <entry>location of the tuple in the heap (page number, row number). See
+         <xref linkend="ddl-system-columns">
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_infomask2</structfield></entry>
+        <entry><type>integer</type></entry>
+        <entry>stores number of attributes (11 bits of the word). The rest are used for flag bits:
+<screen>
+0x2000 - tuple was updated and key cols modified, or tuple deleted
+0x4000 - tuple was HOT-updated
+0x8000 - this is heap-only tuple
+0xE000 - visibility-related bits (so called "hint bits")
+</screen>
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_infomask</structfield></entry>
+        <entry><type>integer</type></entry>
+        <entry>consists of following flag bits:
+<screen>
+0x0001 - has null attribute(s)
+0x0002 - has variable-width attribute(s)
+0x0004 - has external stored attribute(s)
+0x0008 - has an object-id field
+0x0010 - xmax is a key-shared locker
+0x0020 - t_cid is a combo cid
+0x0040 - xmax is exclusive locker
+0x0080 - xmax, if valid, is only a locker
 </screen>
-      See <filename>src/include/storage/itemid.h</> and
-      <filename>src/include/access/htup_details.h</> for explanations of the fields
-      returned.
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_hoff</structfield></entry>
+        <entry><type>smallint</type></entry>
+        <entry>offset to user data: size of header tuple header, plus alingn
+         padding
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_bits</structfield></entry>
+        <entry><type>text</type></entry>
+        <entry>bitmap of nulls converted into human readable text representation
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_oid</structfield></entry>
+        <entry><type>oid</type></entry>
+        <entry>object identifier. Now actually is used only in system tables.
+         See <xref linkend="ddl-system-columns">
+        </entry>
+       </row>
+
+      </tbody>
+     </tgroup>
+    </informaltable>
+
+      For better understanding of these fields see
+      <xref linkend="storage-page-layout">, <xref linkend="ddl-system-columns">
+      and comments from source code <filename>src/include/storage/itemid.h</> and
+      <filename>src/include/access/htup_details.h</>.
+
+     </para>
+    </listitem>
+   </varlistentry>
+
+
+   <varlistentry>
+    <term>
+     <function>tuple_data_split(rel_oid, tuple_data bytea, t_infomask integer, t_infomask2 integer, t_bits text) returs bytea[]</function>
+     <indexterm>
+      <primary>tuple_data_split</primary>
+     </indexterm>
+    </term>
+    <term>
+     <function>tuple_data_split(rel_oid, tuple_data bytea, t_infomask integer, t_infomask2 integer, t_bits text, do_detoast bool) returs bytea[]</function>
+    </term>
+    <term>
+     <function>tuple_data_split(rel_oid, tuple_data bytea, t_infomask integer, t_infomask2 integer, t_bits text, do_detoast bool, warning_mode bool) returs bytea[]</function>
+    </term>
+    <listitem>
+     <para>
+      <function>tuple_data_split</function> splits tuple data into attributes in
+      the same way as <function>nocachegetattr</function> function do it in
+      postgres internals. Returns array of bytea.
+<screen>
+test=# select tuple_data_split('pg_range'::regclass, '\x400f00001700000000000000ba0700004a0f0000520f0000'::bytea, 2304, 6, null);
+                                   tuple_data_split
+---------------------------------------------------------------------------------------
+ {"\\x400f0000","\\x17000000","\\x00000000","\\xba070000","\\x4a0f0000","\\x520f0000"}
+(1 row)
+</screen>
+     </para>
+
+     <para>
+      <function>tuple_data_split</function> takes following arguments:
+
+     <informaltable>
+     <tgroup cols="3">
+      <thead>
+       <row>
+        <entry>Argument</entry>
+        <entry>Type</entry>
+        <entry>Description</entry>
+       </row>
+      </thead>
+
+      <tbody>
+       <row>
+        <entry><structfield>rel_oid</structfield></entry>
+        <entry><type>oid</type></entry>
+        <entry>OID of the relation, of the tuple we want to split</entry>
+       </row>
+
+       <row>
+        <entry><structfield>tuple_data</structfield></entry>
+        <entry><type>bytea</type></entry>
+        <entry>tuple raw data to split
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_infomask</structfield></entry>
+        <entry><type>integer</type></entry>
+        <entry><function>t_infomask</function> as returned by
+         <function>heap_page_items</function>
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_infomask2</structfield></entry>
+        <entry><type>integer</type></entry>
+        <entry><function>t_infomask2</function> as returned by
+         <function>heap_page_items</function>
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>t_bitd</structfield></entry>
+        <entry><type>text</type></entry>
+        <entry>bitmsp of nulls as returned by
+         <function>heap_page_items</function>
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>do_detoast</structfield></entry>
+        <entry><type>bool</type></entry>
+        <entry>optional attribute, if <function>do_detoast</function> is set to
+         true, <function>tuple_data_split</function> will take attribute value
+         from TOAST and uncompress it if it was TOASTed or compressed. See
+         <xref linkend="storage-toast">
+        </entry>
+       </row>
+
+       <row>
+        <entry><structfield>warning_mode</structfield></entry>
+        <entry><type>bool</type></entry>
+        <entry>optional attribute, if <function>warning_mode</function> is set to
+         true, <function>tuple_data_split</function> will not raise ERROR if
+         data consistency check fails, just send WARNING. This may lead to
+         backend crash, use it with care.
+        </entry>
+       </row>
+
+      </tbody>
+     </tgroup>
+    </informaltable>
+     </para>
+     <para>
+       In most cases you will not need <function>tuple_data_split</function>
+       itself, consider using <function>heap_page_item_attrs</function> for
+       viewing page data with split attributes.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>heap_page_item_attrs(page bytea, rel_oid regclass) returns record</function>
+     <indexterm>
+      <primary>heap_page_item_attrs</primary>
+     </indexterm>
+    </term>
+    <term>
+     <function>heap_page_item_attrs(page bytea, rel_oid regclass, do_detoast bool) returns record</function>
+    </term>
+
+    <listitem>
+     <para>
+      <function>heap_page_item_attrs</function> is actually a clone of
+      <function>heap_page_items</function>, with one difference:
+      <function>heap_page_item_attrs</function> returns array of raw values of
+      tuple attributes instead of one peace of raw tuple data. All other return
+      columns are same as in <function>heap_page_items</function>.
+<screen>
+test=# select * from heap_page_item_attrs(get_raw_page('pg_range', 0),'pg_range'::regclass);
+ lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |                                        t_attrs
+----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+--------------------------------------------------------------------------------------
+  1 |   8144 |        1 |     48 |      1 |      0 |        0 | (0,1)  |           6 |       2304 |     24 |        |       | {"\\x400f0000","\\x17000000","\\x00000000","\\xba070000","\\x4a0f0000","\\x520f0000"}
+  2 |   8096 |        1 |     48 |      1 |      0 |        0 | (0,2)  |           6 |       2304 |     24 |        |       | {"\\x420f0000","\\xa4060000","\\x00000000","\\x350c0000","\\x00000000","\\x540f0000"}
+  3 |   8048 |        1 |     48 |      1 |      0 |        0 | (0,3)  |           6 |       2304 |     24 |        |       | {"\\x440f0000","\\x5a040000","\\x00000000","\\x380c0000","\\x00000000","\\x590f0000"}
+  4 |   8000 |        1 |     48 |      1 |      0 |        0 | (0,4)  |           6 |       2304 |     24 |        |       | {"\\x460f0000","\\xa0040000","\\x00000000","\\x370c0000","\\x00000000","\\x5a0f0000"}
+  5 |   7952 |        1 |     48 |      1 |      0 |        0 | (0,5)  |           6 |       2304 |     24 |        |       | {"\\x480f0000","\\x3a040000","\\x00000000","\\x320c0000","\\x4b0f0000","\\x550f0000"}
+  6 |   7904 |        1 |     48 |      1 |      0 |        0 | (0,6)  |           6 |       2304 |     24 |        |       | {"\\x560f0000","\\x14000000","\\x00000000","\\x340c0000","\\x580f0000","\\x530f0000"}
+(6 rows)
+</screen>
+     </para>
+     <para>
+      <function>heap_page_item_attrs</function> takes following argiments:
+     <informaltable>
+     <tgroup cols="3">
+      <thead>
+       <row>
+        <entry>Argument</entry>
+        <entry>Type</entry>
+        <entry>Description</entry>
+       </row>
+      </thead>
+
+      <tbody>
+       <row>
+        <entry><structfield>page</structfield></entry>
+        <entry><type>bytea</type></entry>
+        <entry>page raw data, usually returned by <function>get_raw_page</function></entry>
+       </row>
+
+       <row>
+        <entry><structfield>rel_oid</structfield></entry>
+        <entry><type>oid</type></entry>
+        <entry>OID of the relation, whose page we want to parse</entry>
+       </row>
+
+       <row>
+        <entry><structfield>do_detoast</structfield></entry>
+        <entry><type>bool</type></entry>
+        <entry>optional attribute, if <function>do_detoast</function> is set to
+         true, <function>heap_page_item_attrs</function> will not only split
+         tuple data into attributes, but also try to deTOAST and uncompress
+         attribute value if it was TOASTed or compressed.
+         See <xref linkend="storage-toast">
+        </entry>
+       </row>
+      </tbody>
+     </tgroup>
+    </informaltable>
+
      </para>
     </listitem>
    </varlistentry>
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to