On Wed, Sep 11, 2019 at 1:46 PM Michael Paquier <mich...@paquier.xyz> wrote:
>
> On Tue, Sep 10, 2019 at 08:29:43AM +0530, Amit Kapila wrote:
> > Good thought, but I think even if we want to change the name of
> > tuple_data_split, it might be better done separately.
>
> Yes, that's not the problem of this patch.  Not sure if it actually
> makes sense either to change it.

Hmm it will be more consistent with other functions but I think we
would need to increase the pageinspect version to 1.8 and need the new
sql file to rename the function name. And it will be for PG12, not
PG13. If we have to do it someday I think it's better to do it in PG12
that the table AM has been introduced to. Anyway I've attached
separate patch for it.

>
> The regression tests added are rather unreadable when it comes to
> print a lot of infomask flags.  Could you add at least some unnest()
> calls to the queries using heap_infomask_flags()?  It would make the
> diff lookup much more straight-forward to understand.
>

Seems good idea.

> It would be good to comment as well what 2816 and 1080 stand for.  The
> current code makes it hard to understand for which purpose this is
> used in the tests.

I've reconsidered and updated the regression tests.

>
> +      If decode_combined is set, combination flags like
> Missing a markup here.
>

Fixed.

I've attached the updated patch that incorporated all comments. I kept
the function as superuser-restricted.

Regards,

--
Masahiko Sawada
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index e5a581f141..cfe01297fb 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/pageinspect--1.7--1.8.sql b/contrib/pageinspect/pageinspect--1.7--1.8.sql
new file mode 100644
index 0000000000..39421e5699
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.7--1.8.sql
@@ -0,0 +1,6 @@
+/* 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
+
+ALTER FUNCTION tuple_data_split(oid, bytea, integer, integer, text) RENAME TO heap_tuple_data_split;
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..bfc3a3fdc1 100644
--- a/contrib/pageinspect/sql/page.sql
+++ b/contrib/pageinspect/sql/page.sql
@@ -26,7 +26,7 @@ SELECT pagesize, version FROM page_header(get_raw_page('test1', 0));
 
 SELECT page_checksum(get_raw_page('test1', 0), 0) IS NOT NULL AS silly_checksum_test;
 
-SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bits)
+SELECT heap_tuple_data_split('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));
@@ -49,6 +49,6 @@ drop table test_partitioned;
 create table test8 (f1 int, f2 int, f3 int, f4 int, f5 int, f6 int, f7 int, f8 int);
 insert into test8(f1, f8) values (x'7f00007f'::int, 0);
 select t_bits, t_data from heap_page_items(get_raw_page('test8', 0));
-select tuple_data_split('test8'::regclass, t_data, t_infomask, t_infomask2, t_bits)
+select heap_tuple_data_split('test8'::regclass, t_data, t_infomask, t_infomask2, t_bits)
     from heap_page_items(get_raw_page('test8', 0));
 drop table test8;
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 7a767b25ea..29fc32ab95 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -189,17 +189,17 @@ test=# SELECT * FROM heap_page_items(get_raw_page('pg_class', 0));
 
    <varlistentry>
     <term>
-     <function>tuple_data_split(rel_oid oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text [, do_detoast bool]) returns bytea[]</function>
+     <function>heap_tuple_data_split(rel_oid oid, t_data bytea, t_infomask integer, t_infomask2 integer, t_bits text [, do_detoast bool]) returns bytea[]</function>
      <indexterm>
-      <primary>tuple_data_split</primary>
+      <primary>heap_tuple_data_split</primary>
      </indexterm>
     </term>
     <listitem>
      <para>
-      <function>tuple_data_split</function> splits tuple data into attributes
+      <function>heap_tuple_data_split</function> splits tuple data into attributes
       in the same way as backend internals.
 <screen>
-test=# SELECT tuple_data_split('pg_class'::regclass, t_data, t_infomask, t_infomask2, t_bits) FROM heap_page_items(get_raw_page('pg_class', 0));
+test=# SELECT heap_tuple_data_split('pg_class'::regclass, t_data, t_infomask, t_infomask2, t_bits) FROM heap_page_items(get_raw_page('pg_class', 0));
 </screen>
       This function should be called with the same arguments as the return
       attributes of <function>heap_page_items</function>.
From 45f3f833628a464e77f94b3c7bb4a74f15d3ecfc Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.m...@gmail.com>
Date: Tue, 10 Sep 2019 19:55:55 +0800
Subject: [PATCH v5] Introduce heap_tuple_infomask_flags to decode t_infomask
 and t_infomask2

---
 contrib/pageinspect/Makefile                  |   2 +-
 contrib/pageinspect/expected/page.out         | 136 ++++++++++++++++++
 contrib/pageinspect/heapfuncs.c               | 112 +++++++++++++++
 contrib/pageinspect/pageinspect--1.7--1.8.sql |  15 ++
 contrib/pageinspect/pageinspect.control       |   2 +-
 contrib/pageinspect/sql/page.sql              |  32 +++++
 doc/src/sgml/pageinspect.sgml                 |  39 +++++
 7 files changed, 336 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 e5a581f141..cfe01297fb 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 3fcd9fbe6d..4dea50a67c 100644
--- a/contrib/pageinspect/expected/page.out
+++ b/contrib/pageinspect/expected/page.out
@@ -82,6 +82,142 @@ 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. we show raw flags by
+-- default: HEAP_XMIN_COMMITTED and HEAP_XMIN_INVALID.
+VACUUM FREEZE test1;
+SELECT t_infomask, t_infomask2, unnest(flags)
+FROM heap_page_items(get_raw_page('test1', 0)),
+     LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2) m(flags)
+ORDER BY 3;
+ t_infomask | t_infomask2 |       unnest        
+------------+-------------+---------------------
+       2816 |           2 | HEAP_XMAX_INVALID
+       2816 |           2 | HEAP_XMIN_COMMITTED
+       2816 |           2 | HEAP_XMIN_INVALID
+(3 rows)
+
+-- output the decoded flag HEAP_XMIN_FROZEN instead
+SELECT t_infomask, t_infomask2, unnest(flags)
+FROM heap_page_items(get_raw_page('test1', 0)),
+     LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2, true) m(flags)
+ORDER BY 3;
+ t_infomask | t_infomask2 |      unnest       
+------------+-------------+-------------------
+       2816 |           2 | HEAP_XMAX_INVALID
+       2816 |           2 | HEAP_XMIN_FROZEN
+(2 rows)
+
+-- test for HEAP_LOCKED_UPGRADED
+SELECT unnest(heap_tuple_infomask_flags(x'1080'::integer, 0, true)) ORDER BY 1;
+        unnest        
+----------------------
+ HEAP_LOCKED_UPGRADED
+(1 row)
+
+-- test for all flags of both t_infomask and t_infomask2
+SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, false)) ORDER BY 1;
+        unnest         
+-----------------------
+ HEAP_COMBOCID
+ HEAP_HASEXTERNAL
+ HEAP_HASNULL
+ HEAP_HASOID_OLD
+ HEAP_HASVARWIDTH
+ HEAP_HOT_UPDATED
+ HEAP_KEYS_UPDATED
+ HEAP_MOVED_IN
+ HEAP_MOVED_OFF
+ HEAP_ONLY_TUPLE
+ HEAP_UPDATED
+ HEAP_XMAX_COMMITTED
+ HEAP_XMAX_EXCL_LOCK
+ HEAP_XMAX_INVALID
+ HEAP_XMAX_IS_MULTI
+ HEAP_XMAX_KEYSHR_LOCK
+ HEAP_XMAX_LOCK_ONLY
+ HEAP_XMIN_COMMITTED
+ HEAP_XMIN_INVALID
+(19 rows)
+
+SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, true)) ORDER BY 1;
+       unnest        
+---------------------
+ HEAP_COMBOCID
+ HEAP_HASEXTERNAL
+ HEAP_HASNULL
+ HEAP_HASOID_OLD
+ HEAP_HASVARWIDTH
+ HEAP_HOT_UPDATED
+ HEAP_KEYS_UPDATED
+ HEAP_MOVED
+ HEAP_ONLY_TUPLE
+ HEAP_UPDATED
+ HEAP_XMAX_COMMITTED
+ HEAP_XMAX_INVALID
+ HEAP_XMAX_IS_MULTI
+ HEAP_XMAX_LOCK_ONLY
+ HEAP_XMAX_SHR_LOCK
+ HEAP_XMIN_FROZEN
+(16 rows)
+
+-- same result as specifying all flags
+SELECT unnest(heap_tuple_infomask_flags(-1, -1, false)) ORDER BY 1;
+        unnest         
+-----------------------
+ HEAP_COMBOCID
+ HEAP_HASEXTERNAL
+ HEAP_HASNULL
+ HEAP_HASOID_OLD
+ HEAP_HASVARWIDTH
+ HEAP_HOT_UPDATED
+ HEAP_KEYS_UPDATED
+ HEAP_MOVED_IN
+ HEAP_MOVED_OFF
+ HEAP_ONLY_TUPLE
+ HEAP_UPDATED
+ HEAP_XMAX_COMMITTED
+ HEAP_XMAX_EXCL_LOCK
+ HEAP_XMAX_INVALID
+ HEAP_XMAX_IS_MULTI
+ HEAP_XMAX_KEYSHR_LOCK
+ HEAP_XMAX_LOCK_ONLY
+ HEAP_XMIN_COMMITTED
+ HEAP_XMIN_INVALID
+(19 rows)
+
+SELECT unnest(heap_tuple_infomask_flags(-1, -1, true)) ORDER BY 1;
+       unnest        
+---------------------
+ HEAP_COMBOCID
+ HEAP_HASEXTERNAL
+ HEAP_HASNULL
+ HEAP_HASOID_OLD
+ HEAP_HASVARWIDTH
+ HEAP_HOT_UPDATED
+ HEAP_KEYS_UPDATED
+ HEAP_MOVED
+ HEAP_ONLY_TUPLE
+ HEAP_UPDATED
+ HEAP_XMAX_COMMITTED
+ HEAP_XMAX_INVALID
+ HEAP_XMAX_IS_MULTI
+ HEAP_XMAX_LOCK_ONLY
+ HEAP_XMAX_SHR_LOCK
+ HEAP_XMIN_FROZEN
+(16 rows)
+
+-- output no flags
+SELECT unnest(heap_tuple_infomask_flags(0, 0, false)) ORDER BY 1;
+ unnest 
+--------
+(0 rows)
+
+SELECT unnest(heap_tuple_infomask_flags(0, 0, true)) ORDER BY 1;
+ unnest 
+--------
+(0 rows)
+
 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 64a6e351d5..3764bf375c 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,114 @@ tuple_data_split(PG_FUNCTION_ARGS)
 
 	PG_RETURN_ARRAYTYPE_P(res);
 }
+
+/*
+ * heap_tuple_infomask_flags
+ *
+ * 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_tuple_infomask_flags);
+
+Datum
+heap_tuple_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;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("must be superuser to use raw page functions")));
+
+	bitcnt = pg_popcount((const char *) &t_infomask, sizeof(uint16)) +
+		pg_popcount((const char *) &t_infomask2, sizeof(uint16));
+
+	/* If no flags, return an empty array */
+	if (bitcnt <= 0)
+		PG_RETURN_POINTER(construct_empty_array(TEXTOID));
+
+	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');
+
+	pfree(d);
+
+	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 0000000000..7e85677d6c
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.7--1.8.sql
@@ -0,0 +1,15 @@
+/* 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
+
+--
+-- heap_tuple_infomask_flags()
+--
+CREATE FUNCTION heap_tuple_infomask_flags(
+       t_infomask integer,
+       t_infomask2 integer,
+       decode_combined boolean DEFAULT false)
+RETURNS text[]
+AS 'MODULE_PATHNAME', 'heap_tuple_infomask_flags'
+LANGUAGE C STRICT PARALLEL SAFE;
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..8d3b69aa2b 100644
--- a/contrib/pageinspect/sql/page.sql
+++ b/contrib/pageinspect/sql/page.sql
@@ -31,6 +31,38 @@ 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. we show raw flags by
+-- default: HEAP_XMIN_COMMITTED and HEAP_XMIN_INVALID.
+VACUUM FREEZE test1;
+
+SELECT t_infomask, t_infomask2, unnest(flags)
+FROM heap_page_items(get_raw_page('test1', 0)),
+     LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2) m(flags)
+ORDER BY 3;
+
+-- output the decoded flag HEAP_XMIN_FROZEN instead
+SELECT t_infomask, t_infomask2, unnest(flags)
+FROM heap_page_items(get_raw_page('test1', 0)),
+     LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2, true) m(flags)
+ORDER BY 3;
+
+-- test for HEAP_LOCKED_UPGRADED
+SELECT unnest(heap_tuple_infomask_flags(x'1080'::integer, 0, true)) ORDER BY 1;
+
+-- test for all flags of both t_infomask and t_infomask2
+SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, false)) ORDER BY 1;
+SELECT unnest(heap_tuple_infomask_flags(x'FFFF'::integer, x'FFFF'::integer, true)) ORDER BY 1;
+
+-- same result as specifying all flags
+SELECT unnest(heap_tuple_infomask_flags(-1, -1, false)) ORDER BY 1;
+SELECT unnest(heap_tuple_infomask_flags(-1, -1, true)) ORDER BY 1;
+
+-- output no flags
+SELECT unnest(heap_tuple_infomask_flags(0, 0, false)) ORDER BY 1;
+SELECT unnest(heap_tuple_infomask_flags(0, 0, true)) ORDER BY 1;
+
+
 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 7a767b25ea..367b6327b7 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -184,6 +184,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_tuple_infomask_flags</function> function can be used to unpack the
+      recognized bits of the infomasks of heap tuples.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -236,6 +240,41 @@ test=# SELECT * FROM heap_page_item_attrs(get_raw_page('pg_class', 0), 'pg_class
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>heap_tuple_infomask_flags(t_infomask integer, t_infomask2 integer, decode_combined bool) returns text[]</function>
+     <indexterm>
+      <primary>heap_tuple_infomask_flags</primary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      <function>heap_tuple_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.  For example:
+<screen>
+test=# SELECT heap_tuple_infomask_flags(t_infomask, t_infomask2, true) FROM heap_page_items(get_raw_page('pg_class', 0));
+</screen>
+      This function should be called with the same arguments as the return
+      attributes of <function>heap_page_items</function>.
+     </para>
+     <para>
+      If <parameter>decode_combined</parameter> is <literal>true</literal>,
+      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.22.0

Reply via email to