commit 89ec8b9cf336f14201d35218054d033d11717118
Author: jcoleman <jtc331@gmail.com>
Date:   Mon Oct 15 09:18:28 2018 -0400

    WIP - Add tuple_data_record() to pageinspect

diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index e5a581f141..a1945c5caa 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -5,7 +5,8 @@ OBJS		= rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o \
 		  brinfuncs.o ginfuncs.o hashfuncs.o $(WIN32RES)
 
 EXTENSION = pageinspect
-DATA =  pageinspect--1.6--1.7.sql \
+DATA =  pageinspect--1.7--1.8.sql \
+	pageinspect--1.6--1.7.sql \
 	pageinspect--1.5.sql pageinspect--1.5--1.6.sql \
 	pageinspect--1.4--1.5.sql pageinspect--1.3--1.4.sql \
 	pageinspect--1.2--1.3.sql pageinspect--1.1--1.2.sql \
diff --git a/contrib/pageinspect/expected/page.out b/contrib/pageinspect/expected/page.out
index 3fcd9fbe6d..25e4c1a9ae 100644
--- a/contrib/pageinspect/expected/page.out
+++ b/contrib/pageinspect/expected/page.out
@@ -62,6 +62,13 @@ SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bi
  {"\\x01000001","\\x00020200"}
 (1 row)
 
+SELECT tuple_data_record('test1'::regclass, t_data, t_infomask, t_infomask2, t_bits)
+    FROM heap_page_items(get_raw_page('test1', 0));
+ tuple_data_record 
+-------------------
+ (16777217,131584)
+(1 row)
+
 SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0));
  fsm_page_contents 
 -------------------
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index d96ba1e8b6..5996e260c7 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -277,33 +277,22 @@ heap_page_items(PG_FUNCTION_ARGS)
 }
 
 /*
- * tuple_data_split_internal
+ * parse_tuple_attr_info
  *
- * Split raw tuple data taken directly from a page into an array of bytea
- * elements. This routine does a lookup on NULL values and creates array
- * elements accordingly. This is a reimplementation of nocachegetattr()
- * in heaptuple.c simplified for educational purposes.
+ * Read raw tuple data and determine attribute offset, length, and null status.
+ * This is a reimplementation of nocachegetattr() in heaptuple.c simplified
+ * for educational purposes.
  */
-static Datum
-tuple_data_split_internal(Oid relid, char *tupdata,
-						  uint16 tupdata_len, uint16 t_infomask,
-						  uint16 t_infomask2, bits8 *t_bits,
-						  bool do_detoast)
+static void
+parse_tuple_attr_info(TupleDesc tupdesc, char *tupdata, uint16 tupdata_len,
+		uint16 t_infomask, uint16 t_infomask2, bits8 *t_bits,
+		bool *is_null, int *offsets, int *lengths, bool *varlen)
 {
-	ArrayBuildState *raw_attrs;
-	int			nattrs;
 	int			i;
-	int			off = 0;
-	Relation	rel;
-	TupleDesc	tupdesc;
+	int nattrs;
+	int off = 0;
 
-	/* Get tuple descriptor from relation OID */
-	rel = relation_open(relid, AccessShareLock);
-	tupdesc = RelationGetDescr(rel);
-
-	raw_attrs = initArrayResult(BYTEAOID, CurrentMemoryContext, false);
 	nattrs = tupdesc->natts;
-
 	if (nattrs < (t_infomask2 & HEAP_NATTS_MASK))
 		ereport(ERROR,
 				(errcode(ERRCODE_DATA_CORRUPTED),
@@ -312,8 +301,7 @@ tuple_data_split_internal(Oid relid, char *tupdata,
 	for (i = 0; i < nattrs; i++)
 	{
 		Form_pg_attribute attr;
-		bool		is_null;
-		bytea	   *attr_data = NULL;
+		bool attr_isnull;
 
 		attr = TupleDescAttr(tupdesc, i);
 
@@ -324,16 +312,20 @@ tuple_data_split_internal(Oid relid, char *tupdata,
 		 * (t_infomask2 & HEAP_NATTS_MASK) should be treated as NULL.
 		 */
 		if (i >= (t_infomask2 & HEAP_NATTS_MASK))
-			is_null = true;
+			attr_isnull = true;
 		else
-			is_null = (t_infomask & HEAP_HASNULL) && att_isnull(i, t_bits);
+			attr_isnull = (t_infomask & HEAP_HASNULL) && att_isnull(i, t_bits);
 
-		if (!is_null)
+		is_null[i] = attr_isnull;
+
+		if (!attr_isnull)
 		{
-			int			len;
+			int len;
 
 			if (attr->attlen == -1)
 			{
+				if (varlen)
+					varlen[i] = true;
 				off = att_align_pointer(off, attr->attalign, -1,
 										tupdata + off);
 
@@ -362,77 +354,137 @@ tuple_data_split_internal(Oid relid, char *tupdata,
 						(errcode(ERRCODE_DATA_CORRUPTED),
 						 errmsg("unexpected end of tuple data")));
 
-			if (attr->attlen == -1 && do_detoast)
-				attr_data = DatumGetByteaPCopy(tupdata + off);
-			else
-			{
-				attr_data = (bytea *) palloc(len + VARHDRSZ);
-				SET_VARSIZE(attr_data, len + VARHDRSZ);
-				memcpy(VARDATA(attr_data), tupdata + off, len);
-			}
+			lengths[i] = len;
+			offsets[i] = off;
 
 			off = att_addlength_pointer(off, attr->attlen,
 										tupdata + off);
 		}
+	}
+
+	if (tupdata_len != off)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("end of tuple reached without looking at all its data")));
+}
+
+
+/*
+ * tuple_data_split_internal
+ *
+ * Split raw tuple data taken directly from a page into an array of bytea
+ * elements. This routine does a lookup on NULL values and creates array
+ * elements accordingly.
+ */
+static Datum
+tuple_data_split_internal(Oid relid, char *tupdata,
+			uint16 tupdata_len, uint16 t_infomask,
+			uint16 t_infomask2, bits8 *t_bits,
+			bool do_detoast)
+{
+	ArrayBuildState *raw_attrs;
+	int			nattrs;
+	int			i;
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool *isnull;
+	bool *varlen;
+	int *offsets;
+	int *lengths;
+
+	/* Get tuple descriptor from relation OID */
+	rel = relation_open(relid, AccessShareLock);
+	tupdesc = RelationGetDescr(rel);
+
+	raw_attrs = initArrayResult(BYTEAOID, CurrentMemoryContext, false);
+	nattrs = tupdesc->natts;
+	isnull = (bool *)palloc(sizeof(bool) * nattrs);
+	varlen = (bool *)palloc0(sizeof(bool) * nattrs);
+	offsets = (int *)palloc(sizeof(int) * nattrs);
+	lengths = (int *)palloc(sizeof(int) * nattrs);
+
+	parse_tuple_attr_info(tupdesc, tupdata, tupdata_len, t_infomask, t_infomask2, t_bits,
+		isnull, offsets, lengths, varlen);
+
+	/* Fetch all tuple attribute values */
+	for (i = 0; i < nattrs; i++)
+	{
+		bytea *attr_data = NULL;
+			if (varlen[i] && do_detoast)
+				attr_data = DatumGetByteaPCopy(tupdata + offsets[i]);
+			else
+			{
+				attr_data = (bytea *) palloc(lengths[i] + VARHDRSZ);
+				SET_VARSIZE(attr_data, lengths[i] + VARHDRSZ);
+				memcpy(VARDATA(attr_data), tupdata + offsets[i], lengths[i]);
+			}
 
 		raw_attrs = accumArrayResult(raw_attrs, PointerGetDatum(attr_data),
-									 is_null, BYTEAOID, CurrentMemoryContext);
+									 isnull[i], BYTEAOID, CurrentMemoryContext);
 		if (attr_data)
 			pfree(attr_data);
 	}
 
-	if (tupdata_len != off)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg("end of tuple reached without looking at all its data")));
-
 	relation_close(rel, AccessShareLock);
 
 	return makeArrayResult(raw_attrs, CurrentMemoryContext);
 }
 
 /*
- * tuple_data_split
+ * tuple_data_record_internal
  *
- * Split raw tuple data taken directly from page into distinct elements
- * taking into account null values.
+ * Split raw tuple data taken directly from a page into record of the page's
+ * relation's row tyoe.
  */
-PG_FUNCTION_INFO_V1(tuple_data_split);
+static Datum
+tuple_data_split_internal_record(Oid relid, char *tupdata,
+			uint16 tupdata_len, uint16 t_infomask,
+			uint16 t_infomask2, bits8 *t_bits)
+{
+	int i;
+	int nattrs;
+	Relation	rel;
+	TupleDesc	tupdesc;
+	Datum *values;
+	bool *isnull;
+	int *offsets;
+	int *lengths;
+
+	/* Get tuple descriptor from relation OID */
+	rel = relation_open(relid, AccessShareLock);
+	tupdesc = RelationGetDescr(rel);
+
+	nattrs = tupdesc->natts;
+	isnull = (bool *)palloc(sizeof(bool) * nattrs);
+	offsets = (int *)palloc(sizeof(int) * nattrs);
+	lengths = (int *)palloc(sizeof(int) * nattrs);
+	values = palloc(sizeof(Datum) * nattrs);
+
+	parse_tuple_attr_info(tupdesc, tupdata, tupdata_len, t_infomask, t_infomask2, t_bits,
+		isnull, offsets, lengths, NULL);
+
+	/* Fetch all tuple attribute values */
+	for (i = 0; i < tupdesc->natts; i++)
+	{
+		values[i] = fetchatt(TupleDescAttr(tupdesc, i), tupdata + offsets[i]);
+	}
 
-Datum
-tuple_data_split(PG_FUNCTION_ARGS)
+	relation_close(rel, AccessShareLock);
+
+	return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
+}
+
+/*
+ * parse_and_verify_t_bits
+ *
+ * Convert t_bits string back to the bits8 array as represented in the
+ * tuple header and error check results.
+ */
+static bits8 *
+parse_and_verify_t_bits(char *t_bits_str, uint16 t_infomask, uint16 t_infomask2)
 {
-	Oid			relid;
-	bytea	   *raw_data;
-	uint16		t_infomask;
-	uint16		t_infomask2;
-	char	   *t_bits_str;
-	bool		do_detoast = false;
-	bits8	   *t_bits = NULL;
-	Datum		res;
-
-	relid = PG_GETARG_OID(0);
-	raw_data = PG_ARGISNULL(1) ? NULL : PG_GETARG_BYTEA_P(1);
-	t_infomask = PG_GETARG_INT16(2);
-	t_infomask2 = PG_GETARG_INT16(3);
-	t_bits_str = PG_ARGISNULL(4) ? NULL :
-		text_to_cstring(PG_GETARG_TEXT_PP(4));
-
-	if (PG_NARGS() >= 6)
-		do_detoast = PG_GETARG_BOOL(5);
-
-	if (!superuser())
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to use raw page functions")));
-
-	if (!raw_data)
-		PG_RETURN_NULL();
-
-	/*
-	 * Convert t_bits string back to the bits8 array as represented in the
-	 * tuple header.
-	 */
+	bits8	*t_bits = NULL;
+
 	if (t_infomask & HEAP_HASNULL)
 	{
 		int			bits_str_len;
@@ -464,6 +516,49 @@ tuple_data_split(PG_FUNCTION_ARGS)
 							strlen(t_bits_str))));
 	}
 
+	return t_bits;
+}
+
+/*
+ * tuple_data_split
+ *
+ * Split raw tuple data taken directly from page into distinct elements
+ * taking into account null values.
+ */
+PG_FUNCTION_INFO_V1(tuple_data_split);
+
+Datum
+tuple_data_split(PG_FUNCTION_ARGS)
+{
+	Oid			relid;
+	bytea	   *raw_data;
+	uint16		t_infomask;
+	uint16		t_infomask2;
+	char	   *t_bits_str;
+	bool		do_detoast = false;
+	bits8	   *t_bits = NULL;
+	Datum		res;
+
+	relid = PG_GETARG_OID(0);
+	raw_data = PG_ARGISNULL(1) ? NULL : PG_GETARG_BYTEA_P(1);
+	t_infomask = PG_GETARG_INT16(2);
+	t_infomask2 = PG_GETARG_INT16(3);
+	t_bits_str = PG_ARGISNULL(4) ? NULL :
+		text_to_cstring(PG_GETARG_TEXT_PP(4));
+
+	if (PG_NARGS() >= 6)
+		do_detoast = PG_GETARG_BOOL(5);
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("must be superuser to use raw page functions")));
+
+	if (!raw_data)
+		PG_RETURN_NULL();
+
+	t_bits = parse_and_verify_t_bits(t_bits_str, t_infomask, t_infomask2);
+
 	/* Split tuple data */
 	res = tuple_data_split_internal(relid, (char *) raw_data + VARHDRSZ,
 									VARSIZE(raw_data) - VARHDRSZ,
@@ -475,3 +570,43 @@ tuple_data_split(PG_FUNCTION_ARGS)
 
 	PG_RETURN_ARRAYTYPE_P(res);
 }
+
+PG_FUNCTION_INFO_V1(tuple_data_record);
+
+Datum
+tuple_data_record(PG_FUNCTION_ARGS)
+{
+	Oid	relid;
+	bytea *raw_data;
+	uint16 t_infomask;
+	uint16 t_infomask2;
+	char *t_bits_str;
+	bits8 *t_bits = NULL;
+	Datum res;
+
+	relid = PG_GETARG_OID(0);
+	raw_data = PG_ARGISNULL(1) ? NULL : PG_GETARG_BYTEA_P(1);
+	t_infomask = PG_GETARG_INT16(2);
+	t_infomask2 = PG_GETARG_INT16(3);
+	t_bits_str = PG_ARGISNULL(4) ? NULL :
+		text_to_cstring(PG_GETARG_TEXT_PP(4));
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("must be superuser to use raw page functions")));
+
+	if (!raw_data)
+		PG_RETURN_NULL();
+
+	t_bits = parse_and_verify_t_bits(t_bits_str, t_infomask, t_infomask2);
+
+	res = tuple_data_split_internal_record(relid, (char *) raw_data + VARHDRSZ,
+									VARSIZE(raw_data) - VARHDRSZ,
+									t_infomask, t_infomask2, t_bits);
+
+	if (t_bits)
+		pfree(t_bits);
+
+	return res;
+}
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index dcfc61f22d..f8cdf526c6 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.7'
+default_version = '1.8'
 module_pathname = '$libdir/pageinspect'
 relocatable = true
diff --git a/contrib/pageinspect/sql/page.sql b/contrib/pageinspect/sql/page.sql
index 8ac9991837..08fd79248a 100644
--- a/contrib/pageinspect/sql/page.sql
+++ b/contrib/pageinspect/sql/page.sql
@@ -29,6 +29,9 @@ SELECT page_checksum(get_raw_page('test1', 0), 0) IS NOT NULL AS silly_checksum_
 SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bits)
     FROM heap_page_items(get_raw_page('test1', 0));
 
+SELECT tuple_data_record('test1'::regclass, t_data, t_infomask, t_infomask2, t_bits)
+    FROM heap_page_items(get_raw_page('test1', 0));
+
 SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0));
 
 DROP TABLE test1;
