From 018077d786f874cb314b5f61b5ef85f42c62bbe5 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Fri, 23 Aug 2019 10:36:13 +0900
Subject: [PATCH v3] Introduce heap_infomask_flags to decode infomask and
 infomask2

---
 contrib/pageinspect/Makefile                  |   2 +-
 contrib/pageinspect/expected/page.out         |  97 ++++++++++++++++++++++++
 contrib/pageinspect/heapfuncs.c               | 104 ++++++++++++++++++++++++++
 contrib/pageinspect/pageinspect--1.7--1.8.sql |  13 ++++
 contrib/pageinspect/pageinspect.control       |   2 +-
 contrib/pageinspect/sql/page.sql              |  26 +++++++
 doc/src/sgml/pageinspect.sgml                 |  33 ++++++++
 7 files changed, 275 insertions(+), 2 deletions(-)
 create mode 100644 contrib/pageinspect/pageinspect--1.7--1.8.sql

diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index e5a581f..cfe0129 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 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 3fcd9fb..3b47599 100644
--- a/contrib/pageinspect/expected/page.out
+++ b/contrib/pageinspect/expected/page.out
@@ -82,6 +82,103 @@ 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_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_XMAX_INVALID,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID}
+(1 row)
+
+SELECT heap_infomask_flags(2816, 0); -- show raw flags by default
+                    heap_infomask_flags                    
+-----------------------------------------------------------
+ {HEAP_XMAX_INVALID,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID}
+(1 row)
+
+SELECT heap_infomask_flags(2816, 0, true);
+         heap_infomask_flags          
+--------------------------------------
+ {HEAP_XMAX_INVALID,HEAP_XMIN_FROZEN}
+(1 row)
+
+SELECT heap_infomask_flags(2816, 1, true);
+         heap_infomask_flags          
+--------------------------------------
+ {HEAP_XMAX_INVALID,HEAP_XMIN_FROZEN}
+(1 row)
+
+SELECT heap_infomask_flags(2816, 1, false);
+                    heap_infomask_flags                    
+-----------------------------------------------------------
+ {HEAP_XMAX_INVALID,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID}
+(1 row)
+
+SELECT heap_infomask_flags(2816, x'FFFF'::integer, true);
+                                   heap_infomask_flags                                   
+-----------------------------------------------------------------------------------------
+ {HEAP_XMAX_INVALID,HEAP_XMIN_FROZEN,HEAP_KEYS_UPDATED,HEAP_HOT_UPDATED,HEAP_ONLY_TUPLE}
+(1 row)
+
+SELECT heap_infomask_flags(2816, x'FFFF'::integer, false);
+                                             heap_infomask_flags                                              
+--------------------------------------------------------------------------------------------------------------
+ {HEAP_XMAX_INVALID,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_KEYS_UPDATED,HEAP_HOT_UPDATED,HEAP_ONLY_TUPLE}
+(1 row)
+
+SELECT heap_infomask_flags(x'1080'::integer, 0, true); -- test for HEAP_LOCKED_UPGRADED
+  heap_infomask_flags   
+------------------------
+ {HEAP_LOCKED_UPGRADED}
+(1 row)
+
+SELECT heap_infomask_flags(x'1080'::integer, 0, false);
+           heap_infomask_flags            
+------------------------------------------
+ {HEAP_XMAX_LOCK_ONLY,HEAP_XMAX_IS_MULTI}
+(1 row)
+
+SELECT heap_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, false);
+                                                                                                                                                           heap_infomask_flags                                                                                                                                                            
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {HEAP_HASNULL,HEAP_HASVARWIDTH,HEAP_HASEXTERNAL,HEAP_HASOID_OLD,HEAP_COMBOCID,HEAP_XMAX_COMMITTED,HEAP_XMAX_INVALID,HEAP_UPDATED,HEAP_XMAX_EXCL_LOCK,HEAP_XMAX_KEYSHR_LOCK,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_MOVED_IN,HEAP_MOVED_OFF,HEAP_XMAX_LOCK_ONLY,HEAP_XMAX_IS_MULTI,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_OLD,HEAP_COMBOCID,HEAP_XMAX_COMMITTED,HEAP_XMAX_INVALID,HEAP_UPDATED,HEAP_XMAX_SHR_LOCK,HEAP_XMIN_FROZEN,HEAP_MOVED,HEAP_XMAX_LOCK_ONLY,HEAP_XMAX_IS_MULTI,HEAP_KEYS_UPDATED,HEAP_HOT_UPDATED,HEAP_ONLY_TUPLE}
+(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_OLD,HEAP_COMBOCID,HEAP_XMAX_COMMITTED,HEAP_XMAX_INVALID,HEAP_UPDATED,HEAP_XMAX_EXCL_LOCK,HEAP_XMAX_KEYSHR_LOCK,HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_MOVED_IN,HEAP_MOVED_OFF,HEAP_XMAX_LOCK_ONLY,HEAP_XMAX_IS_MULTI,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 or index
 -- would fail
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 64a6e35..0a89d47 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_am_d.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
+#include "port/pg_bitutils.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
@@ -494,3 +495,106 @@ tuple_data_split(PG_FUNCTION_ARGS)
 
 	PG_RETURN_ARRAYTYPE_P(res);
 }
+
+/*
+ * Decode an infomask, per htup_details.c, into human readable
+ * form. For detail of masks see access/htup_details.h.
+ */
+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	decode_combined = PG_GETARG_BOOL(2);
+	int		cnt = 0;
+	ArrayType *a;
+	int		bitcnt;
+	Datum	*d;
+
+	bitcnt = pg_popcount32(t_infomask) + pg_popcount32(t_infomask2);
+	if (bitcnt == 0)
+	{
+		/* If no flags, return an empty array */
+		a = construct_empty_array(TEXTOID);
+		PG_RETURN_POINTER(a);
+	}
+
+	d = (Datum *) palloc0(sizeof(Datum) * bitcnt);
+
+	/* decode t_infomask */
+	if ((t_infomask & HEAP_HASNULL) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_HASNULL");
+	if ((t_infomask & HEAP_HASVARWIDTH) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_HASVARWIDTH");
+	if ((t_infomask & HEAP_HASEXTERNAL) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_HASEXTERNAL");
+	if ((t_infomask & HEAP_HASOID_OLD) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_HASOID_OLD");
+	if ((t_infomask & HEAP_COMBOCID) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_COMBOCID");
+	if ((t_infomask & HEAP_XMAX_COMMITTED) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_XMAX_COMMITTED");
+	if ((t_infomask & HEAP_XMAX_INVALID) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_XMAX_INVALID");
+	if ((t_infomask & HEAP_UPDATED) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_UPDATED");
+
+	/* decode combined masks of t_infomaks */
+	if (decode_combined &&
+		(t_infomask & HEAP_XMAX_SHR_LOCK) != 0)
+			d[cnt++] = CStringGetTextDatum("HEAP_XMAX_SHR_LOCK");
+	else
+	{
+		if ((t_infomask & HEAP_XMAX_EXCL_LOCK) != 0)
+			d[cnt++] = CStringGetTextDatum("HEAP_XMAX_EXCL_LOCK");
+		if ((t_infomask & HEAP_XMAX_KEYSHR_LOCK) != 0)
+			d[cnt++] = CStringGetTextDatum("HEAP_XMAX_KEYSHR_LOCK");
+	}
+
+	if (decode_combined &&
+		(t_infomask & HEAP_XMIN_FROZEN) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_XMIN_FROZEN");
+	else
+	{
+		if ((t_infomask & HEAP_XMIN_COMMITTED) != 0)
+			d[cnt++] = CStringGetTextDatum("HEAP_XMIN_COMMITTED");
+		if ((t_infomask & HEAP_XMIN_INVALID) != 0)
+			d[cnt++] = CStringGetTextDatum("HEAP_XMIN_INVALID");
+	}
+
+	if (decode_combined &&
+		(t_infomask & HEAP_MOVED) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_MOVED");
+	else
+	{
+		if ((t_infomask & HEAP_MOVED_IN) != 0)
+			d[cnt++] = CStringGetTextDatum("HEAP_MOVED_IN");
+		if ((t_infomask & HEAP_MOVED_OFF) != 0)
+			d[cnt++] = CStringGetTextDatum("HEAP_MOVED_OFF");
+	}
+
+	if (decode_combined &&
+		HEAP_LOCKED_UPGRADED(t_infomask))
+		d[cnt++] = CStringGetTextDatum("HEAP_LOCKED_UPGRADED");
+	else
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask))
+			d[cnt++] = CStringGetTextDatum("HEAP_XMAX_LOCK_ONLY");
+		if ((t_infomask & HEAP_XMAX_IS_MULTI) != 0)
+			d[cnt++] = CStringGetTextDatum("HEAP_XMAX_IS_MULTI");
+	}
+
+	/* decode t_infomask2 */
+	if ((t_infomask2 & HEAP_KEYS_UPDATED) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_KEYS_UPDATED");
+	if ((t_infomask2 & HEAP_HOT_UPDATED) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_HOT_UPDATED");
+	if ((t_infomask2 & HEAP_ONLY_TUPLE) != 0)
+		d[cnt++] = CStringGetTextDatum("HEAP_ONLY_TUPLE");
+
+	a = construct_array(d, cnt, TEXTOID, -1, false, 'i');
+
+	PG_RETURN_POINTER(a);
+}
diff --git a/contrib/pageinspect/pageinspect--1.7--1.8.sql b/contrib/pageinspect/pageinspect--1.7--1.8.sql
new file mode 100644
index 0000000..aaf9399
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.7--1.8.sql
@@ -0,0 +1,13 @@
+/* contrib/pageinspect/pageinspect--1.7--1.8.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.8'" to load this file. \quit
+
+-- decode infomask flags as human readable flag names
+CREATE FUNCTION heap_infomask_flags(
+       infomask integer,
+       infomask2 integer,
+       decode_combined boolean DEFAULT false)
+RETURNS text[]
+AS 'MODULE_PATHNAME', 'heap_infomask_flags'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index dcfc61f..f8cdf52 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 8ac9991..4ac96ef 100644
--- a/contrib/pageinspect/sql/page.sql
+++ b/contrib/pageinspect/sql/page.sql
@@ -31,6 +31,32 @@ 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); -- show raw flags by default
+SELECT heap_infomask_flags(2816, 0, true);
+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'1080'::integer, 0, true); -- test for HEAP_LOCKED_UPGRADED
+SELECT heap_infomask_flags(x'1080'::integer, 0, 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 or index
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 0b92025..258a0f1 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -180,6 +180,10 @@ test=# SELECT * FROM heap_page_items(get_raw_page('pg_class', 0));
       <filename>src/include/access/htup_details.h</filename> 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>
 
@@ -232,6 +236,35 @@ test=# SELECT * FROM heap_page_item_attrs(get_raw_page('pg_class', 0), 'pg_class
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>heap_infomask_flags(infomask integer, infomask2 integer, decode_combined bool) returns text[]</function>
+     <indexterm>
+      <primary>heap_infomask_flags</primary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      <function>heap_infomask_flags</function> decodes the
+      <structfield>t_infomask</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 decode_combined is set, combination flags like
+      <literal>HEAP_XMIN_FROZEN</literal> are output instead of raw
+      flags, <literal>HEAP_XMIN_COMMITTED</literal> and
+      <literal>HEAP_XMIN_INVALID</literal>. Default value is
+      <literal>false</literal>.
+     </para>
+     <para>
+      For the meaning of these flags see
+      <filename>src/include/access/htup_details.h</filename>
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </sect2>
 
-- 
2.10.5

