From a25c1e50e3b3ff493a018ab594a71dc3d64832ee Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Thu, 20 Jul 2017 11:20:21 +0800
Subject: [PATCH v2] Introduce heap_infomask_flags to decode infomask and
 infomask2

---
 contrib/pageinspect/Makefile                  |   3 +-
 contrib/pageinspect/expected/page.out         |  85 ++++++++++++++++++
 contrib/pageinspect/heapfuncs.c               | 120 ++++++++++++++++++++++++++
 contrib/pageinspect/pageinspect--1.6--1.7.sql |   9 ++
 contrib/pageinspect/pageinspect.control       |   2 +-
 contrib/pageinspect/sql/page.sql              |  24 ++++++
 doc/src/sgml/pageinspect.sgml                 |  32 +++++++
 7 files changed, 273 insertions(+), 2 deletions(-)
 create mode 100644 contrib/pageinspect/pageinspect--1.6--1.7.sql

diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 0a3cbee..de114c7 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.5.sql pageinspect--1.5--1.6.sql \
+DATA = 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 \
 	pageinspect--1.0--1.1.sql pageinspect--unpackaged--1.0.sql
diff --git a/contrib/pageinspect/expected/page.out b/contrib/pageinspect/expected/page.out
index 8e15947..b335265 100644
--- a/contrib/pageinspect/expected/page.out
+++ b/contrib/pageinspect/expected/page.out
@@ -82,6 +82,91 @@ SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0));
  
 (1 row)
 
+-- If we freeze the only tuple on test1, the infomask should
+-- always be the same in all test runs.
+VACUUM FREEZE test1;
+SELECT t_infomask, t_infomask2, flags
+FROM heap_page_items(get_raw_page('test1', 0)),
+     LATERAL heap_infomask_flags(t_infomask, t_infomask2, true) m(flags);
+ t_infomask | t_infomask2 |                                   flags                                    
+------------+-------------+----------------------------------------------------------------------------
+       2816 |           2 | {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_INVALID,HEAP_XMIN_FROZEN}
+(1 row)
+
+SELECT t_infomask, t_infomask2, flags
+FROM heap_page_items(get_raw_page('test1', 0)),
+     LATERAL heap_infomask_flags(t_infomask, t_infomask2, false) m(flags);
+ t_infomask | t_infomask2 |                           flags                           
+------------+-------------+-----------------------------------------------------------
+       2816 |           2 | {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_INVALID}
+(1 row)
+
+SELECT heap_infomask_flags(2816, 0);
+                            heap_infomask_flags                             
+----------------------------------------------------------------------------
+ {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_INVALID,HEAP_XMIN_FROZEN}
+(1 row)
+
+SELECT heap_infomask_flags(2816, 0, false);
+                    heap_infomask_flags                    
+-----------------------------------------------------------
+ {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_INVALID}
+(1 row)
+
+SELECT heap_infomask_flags(2816, 1, true);
+                            heap_infomask_flags                             
+----------------------------------------------------------------------------
+ {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_INVALID,HEAP_XMIN_FROZEN}
+(1 row)
+
+SELECT heap_infomask_flags(2816, 1, false);
+                    heap_infomask_flags                    
+-----------------------------------------------------------
+ {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_INVALID}
+(1 row)
+
+SELECT heap_infomask_flags(2816, x'FFFF'::integer, true);
+                                                      heap_infomask_flags                                                      
+-------------------------------------------------------------------------------------------------------------------------------
+ {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_INVALID,HEAP_KEYS_UPDATED,HEAP_HOT_UPDATED,HEAP_ONLY_TUPLE,HEAP_XMIN_FROZEN}
+(1 row)
+
+SELECT heap_infomask_flags(2816, x'FFFF'::integer, false);
+                                             heap_infomask_flags                                              
+--------------------------------------------------------------------------------------------------------------
+ {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_INVALID,HEAP_KEYS_UPDATED,HEAP_HOT_UPDATED,HEAP_ONLY_TUPLE}
+(1 row)
+
+SELECT heap_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, false);
+                                                                                                                                                         heap_infomask_flags                                                                                                                                                          
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {HEAP_HASNULL,HEAP_HASVARWIDTH,HEAP_HASEXTERNAL,HEAP_HASOID,HEAP_XMAX_KEYSHR_LOCK,HEAP_COMBOCID,HEAP_XMAX_EXCL_LOCK,HEAP_XMAX_LOCK_ONLY,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_COMMITTED,HEAP_XMAX_INVALID,HEAP_XMAX_IS_MULTI,HEAP_UPDATED,HEAP_MOVED_OFF,HEAP_MOVED_IN,HEAP_KEYS_UPDATED,HEAP_HOT_UPDATED,HEAP_ONLY_TUPLE}
+(1 row)
+
+SELECT heap_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, true);
+                                                                                                                                                                              heap_infomask_flags                                                                                                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {HEAP_HASNULL,HEAP_HASVARWIDTH,HEAP_HASEXTERNAL,HEAP_HASOID,HEAP_XMAX_KEYSHR_LOCK,HEAP_COMBOCID,HEAP_XMAX_EXCL_LOCK,HEAP_XMAX_LOCK_ONLY,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_COMMITTED,HEAP_XMAX_INVALID,HEAP_XMAX_IS_MULTI,HEAP_UPDATED,HEAP_MOVED_OFF,HEAP_MOVED_IN,HEAP_KEYS_UPDATED,HEAP_HOT_UPDATED,HEAP_ONLY_TUPLE,HEAP_XMIN_FROZEN,HEAP_XMAX_IS_LOCKED_ONLY}
+(1 row)
+
+SELECT heap_infomask_flags(0, 0, true);
+ heap_infomask_flags 
+---------------------
+ {}
+(1 row)
+
+SELECT heap_infomask_flags(0, 0, false);
+ heap_infomask_flags 
+---------------------
+ {}
+(1 row)
+
+SELECT heap_infomask_flags(-1, -1, false);
+                                                                                                                                                         heap_infomask_flags                                                                                                                                                          
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {HEAP_HASNULL,HEAP_HASVARWIDTH,HEAP_HASEXTERNAL,HEAP_HASOID,HEAP_XMAX_KEYSHR_LOCK,HEAP_COMBOCID,HEAP_XMAX_EXCL_LOCK,HEAP_XMAX_LOCK_ONLY,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_COMMITTED,HEAP_XMAX_INVALID,HEAP_XMAX_IS_MULTI,HEAP_UPDATED,HEAP_MOVED_OFF,HEAP_MOVED_IN,HEAP_KEYS_UPDATED,HEAP_HOT_UPDATED,HEAP_ONLY_TUPLE}
+(1 row)
+
 DROP TABLE test1;
 -- check that using any of these functions with a partitioned table would fail
 create table test_partitioned (a int) partition by range (a);
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 72d1776..2ef5b39 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -478,3 +478,123 @@ tuple_data_split(PG_FUNCTION_ARGS)
 
 	PG_RETURN_ARRAYTYPE_P(res);
 }
+
+/*
+ * Brian Kernighan's popcount algorithm for counting number of set bits in a
+ * mask. The faster methods aren't worth the complexity here.
+ *
+ * See e.g. http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
+ *
+ * gcc has __builtin_popcount but there's no standard alternative.
+ */
+static inline int
+pg_popcount32(uint32 mask)
+{
+	unsigned int c; // c accumulates the total bits set in v
+	for (c = 0; mask; c++)
+	{
+		mask &= mask - 1; // clear the least significant bit set
+	}
+	return c;
+}
+
+/*
+ * Infomask flag names. The infomask2 values are shifted into the
+ * high 16 bits.
+ */
+struct infomask_details
+{
+	uint32 flag_value;
+	const char flag_name[24];
+};
+
+#define MASKINFO_LENGTH 19
+static struct infomask_details maskinfo[MASKINFO_LENGTH] =
+	{
+		/* infomask1 values */
+		{(uint32)HEAP_HASNULL, "HEAP_HASNULL"},
+		{(uint32)HEAP_HASVARWIDTH, "HEAP_HASVARWIDTH"},
+		{(uint32)HEAP_HASEXTERNAL, "HEAP_HASEXTERNAL"},
+		{(uint32)HEAP_HASOID, "HEAP_HASOID"},
+		{(uint32)HEAP_XMAX_KEYSHR_LOCK, "HEAP_XMAX_KEYSHR_LOCK"},
+		{(uint32)HEAP_COMBOCID, "HEAP_COMBOCID"},
+		{(uint32)HEAP_XMAX_EXCL_LOCK, "HEAP_XMAX_EXCL_LOCK"},
+		{(uint32)HEAP_XMAX_LOCK_ONLY, "HEAP_XMAX_LOCK_ONLY"},
+		{(uint32)HEAP_XMIN_COMMITTED, "HEAP_XMIN_COMMITTED"},
+		{(uint32)HEAP_XMIN_INVALID, "HEAP_XMIN_INVALID"},
+		{(uint32)HEAP_XMAX_COMMITTED, "HEAP_XMAX_COMMITTED"},
+		{(uint32)HEAP_XMAX_INVALID, "HEAP_XMAX_INVALID"},
+		{(uint32)HEAP_XMAX_IS_MULTI, "HEAP_XMAX_IS_MULTI"},
+		{(uint32)HEAP_UPDATED, "HEAP_UPDATED"},
+		{(uint32)HEAP_MOVED_OFF, "HEAP_MOVED_OFF"},
+		{(uint32)HEAP_MOVED_IN, "HEAP_MOVED_IN"},
+		/* infomask2 values */
+		{((uint32)HEAP_KEYS_UPDATED)<<16, "HEAP_KEYS_UPDATED"},
+		{((uint32)HEAP_HOT_UPDATED)<<16, "HEAP_HOT_UPDATED"},
+		{((uint32)HEAP_ONLY_TUPLE)<<16, "HEAP_ONLY_TUPLE"},
+	};
+
+/*
+ * Decode an infomask, per htup_details.c, into human readable
+ * form.
+ */
+PG_FUNCTION_INFO_V1(heap_infomask_flags);
+
+Datum
+heap_infomask_flags(PG_FUNCTION_ARGS)
+{
+	uint16 t_infomask = PG_GETARG_INT16(0);
+	uint16 t_infomask2 = PG_GETARG_INT16(1);
+	bool include_combined = PG_GETARG_BOOL(2);
+	uint32 combomask = (((uint32)t_infomask2) << 16) + (uint32)t_infomask;
+	unsigned int maxarray = pg_popcount32(combomask);
+	Datum *d;
+	ArrayType *a;
+	int i;
+	int insertpos;
+
+	Assert((t_infomask == 0 && t_infomask2 == 0) == (maxarray == 0));
+
+	if (maxarray == 0)
+	{
+		a = construct_empty_array(TEXTOID);
+		PG_RETURN_POINTER(a);
+	}
+
+	if (include_combined)
+	{
+		maxarray += (t_infomask & HEAP_XMIN_FROZEN) == HEAP_XMIN_FROZEN;
+		maxarray += HEAP_XMAX_IS_LOCKED_ONLY(t_infomask) != 0;
+		maxarray += HEAP_LOCKED_UPGRADED(t_infomask) != 0;
+	}
+
+	d = (Datum *) palloc0(sizeof(Datum) * maxarray);
+
+	insertpos = 0;
+	for (i = 0; i < MASKINFO_LENGTH; ++i)
+	{
+		if ((combomask & maskinfo[i].flag_value) == maskinfo[i].flag_value)
+			d[insertpos++] = CStringGetTextDatum(maskinfo[i].flag_name);
+	}
+
+	/*
+	 * These tests are useful to report in the mask we output, since they're
+	 * much more simply done here than in SQL, and here they won't get out of
+	 * sync with what Pg does if we change it later.
+	 */
+	if (include_combined)
+	{
+		if ((t_infomask & HEAP_XMIN_FROZEN) == HEAP_XMIN_FROZEN)
+			d[insertpos++] = CStringGetTextDatum("HEAP_XMIN_FROZEN");
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask))
+			d[insertpos++] = CStringGetTextDatum("HEAP_XMAX_IS_LOCKED_ONLY");
+
+		if (HEAP_LOCKED_UPGRADED(t_infomask))
+			d[insertpos++] = CStringGetTextDatum("HEAP_LOCKED_UPGRADED");
+	}
+
+	a = construct_array(d, insertpos, TEXTOID, -1, false, 'i');
+
+	PG_RETURN_POINTER(a);
+}
diff --git a/contrib/pageinspect/pageinspect--1.6--1.7.sql b/contrib/pageinspect/pageinspect--1.6--1.7.sql
new file mode 100644
index 0000000..b3d1fe4
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.6--1.7.sql
@@ -0,0 +1,9 @@
+/* contrib/pageinspect/pageinspect--1.6--1.7.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.7'" to load this file. \quit
+
+-- decode infomask flags as human readable flag names
+CREATE FUNCTION heap_infomask_flags(infomask1 integer, infomask2 integer,
+	include_combined boolean DEFAULT true)
+RETURNS text[] STRICT LANGUAGE 'c' AS 'MODULE_PATHNAME','heap_infomask_flags';
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 1a61c9f..dcfc61f 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.6'
+default_version = '1.7'
 module_pathname = '$libdir/pageinspect'
 relocatable = true
diff --git a/contrib/pageinspect/sql/page.sql b/contrib/pageinspect/sql/page.sql
index 493ca9b..2f06c39 100644
--- a/contrib/pageinspect/sql/page.sql
+++ b/contrib/pageinspect/sql/page.sql
@@ -31,6 +31,30 @@ SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bi
 
 SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0));
 
+-- If we freeze the only tuple on test1, the infomask should
+-- always be the same in all test runs.
+VACUUM FREEZE test1;
+
+SELECT t_infomask, t_infomask2, flags
+FROM heap_page_items(get_raw_page('test1', 0)),
+     LATERAL heap_infomask_flags(t_infomask, t_infomask2, true) m(flags);
+
+SELECT t_infomask, t_infomask2, flags
+FROM heap_page_items(get_raw_page('test1', 0)),
+     LATERAL heap_infomask_flags(t_infomask, t_infomask2, false) m(flags);
+
+SELECT heap_infomask_flags(2816, 0);
+SELECT heap_infomask_flags(2816, 0, false);
+SELECT heap_infomask_flags(2816, 1, true);
+SELECT heap_infomask_flags(2816, 1, false);
+SELECT heap_infomask_flags(2816, x'FFFF'::integer, true);
+SELECT heap_infomask_flags(2816, x'FFFF'::integer, false);
+SELECT heap_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, false);
+SELECT heap_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, true);
+SELECT heap_infomask_flags(0, 0, true);
+SELECT heap_infomask_flags(0, 0, false);
+SELECT heap_infomask_flags(-1, -1, false);
+
 DROP TABLE test1;
 
 -- check that using any of these functions with a partitioned table would fail
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index ccdaf3e..52bb9d5 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -151,6 +151,10 @@ test=# SELECT * FROM heap_page_items(get_raw_page('pg_class', 0));
       <filename>src/include/access/htup_details.h</> for explanations of the fields
       returned.
      </para>
+     <para>
+      The <function>heap_infomask_flags</function> function can be used to unpack the
+      recognised bits of the infomasks of heap tuples.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -206,6 +210,34 @@ test=# SELECT * FROM heap_page_item_attrs(get_raw_page('pg_class', 0), 'pg_class
 
    <varlistentry>
     <term>
+     <function>heap_infomask_flags(infomask1 integer, infomask2 integer, include_combined bool) returns text[]</function>
+     <indexterm>
+      <primary>heap_infomask_flags</primary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      <function>heap_infomask_flags</function> decodes the
+      <structfield>t_infomask1</structfield> and
+      <structfield>t_infomask2</structfield> returned by
+      <function>heap_page_items</function> into a human-readable array of flag
+      names. This can be used to see the tuple hint bits etc.
+     </para>
+     <para>
+      If include_combined is set (the default), combination flags like
+      <literal>HEAP_XMIN_FROZEN</literal> are also output. The original fields
+      are not filtered out, so e.g. a frozen tuple will have
+      <literal>HEAP_XMIN_FROZEN, HEAP_XMIN_COMMITTED, HEAP_XMIN_INVALID</literal>.
+     </para>
+     <para>
+      For the meaning of these flags see
+      <filename>src/include/access/htup_details.h</>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
      <function>fsm_page_contents(page bytea) returns text</function>
      <indexterm>
       <primary>fsm_page_contents</primary>
-- 
2.9.4

