From 88934d500a11e2dc1a20d39a16af1e6fe850a859 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Fri, 7 Apr 2023 14:42:54 -0700
Subject: [PATCH v4] Add rmgr_desc utilities

- Begin to standardize format of rmgr_desc output, adding a new
  rmgr_utils file with comments about expected format.
- Add helper which prints arrays in a standard format and use it in
  heapdesc to print out offset arrays, relid arrays, freeze plan arrays,
  and redirect arrays.

Suggested by Andres Freund

Discussion: https://postgr.es/m/flat/20230109215842.fktuhesvayno6o4g%40awork3.anarazel.de
---
 src/include/access/rmgrdesc_utils.h          |  25 +++
 src/backend/access/rmgrdesc/Makefile         |   1 +
 src/backend/access/rmgrdesc/heapdesc.c       | 159 +++++++++++++++----
 src/backend/access/rmgrdesc/meson.build      |   1 +
 src/backend/access/rmgrdesc/rmgrdesc_utils.c |  83 ++++++++++
 src/bin/pg_waldump/Makefile                  |   2 +-
 doc/src/sgml/pgwalinspect.sgml               |  20 +--
 7 files changed, 252 insertions(+), 39 deletions(-)
 create mode 100644 src/include/access/rmgrdesc_utils.h
 create mode 100644 src/backend/access/rmgrdesc/rmgrdesc_utils.c

diff --git a/src/include/access/rmgrdesc_utils.h b/src/include/access/rmgrdesc_utils.h
new file mode 100644
index 000000000..58da418bb
--- /dev/null
+++ b/src/include/access/rmgrdesc_utils.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * rmgrdesc_utils.h
+ *	  helper utilities for rmgrdesc
+ *
+ * Copyright (c) 2023 PostgreSQL Global Development Group
+ *
+ * src/include/access/rmgrdesc_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RMGRDESC_UTILS_H_
+#define RMGRDESC_UTILS_H_
+
+#include "storage/off.h"
+#include "access/heapam_xlog.h"
+
+extern void array_desc(StringInfo buf, void *array, size_t elem_size, int count,
+					   void (*elem_desc) (StringInfo buf, void *elem, void *data),
+					   void *data);
+extern void offset_elem_desc(StringInfo buf, void *offset, void *data);
+extern void redirect_elem_desc(StringInfo buf, void *offset, void *data);
+extern void relid_desc(StringInfo buf, void *relid, void *data);
+
+#endif							/* RMGRDESC_UTILS_H */
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index f88d72fd8..cd95eec37 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -23,6 +23,7 @@ OBJS = \
 	nbtdesc.o \
 	relmapdesc.o \
 	replorigindesc.o \
+	rmgrdesc_utils.o \
 	seqdesc.o \
 	smgrdesc.o \
 	spgdesc.o \
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index 628f7e821..64427ac56 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -15,20 +15,52 @@
 #include "postgres.h"
 
 #include "access/heapam_xlog.h"
+#include "access/rmgrdesc_utils.h"
 
 static void
 out_infobits(StringInfo buf, uint8 infobits)
 {
+	if ((infobits & XLHL_XMAX_IS_MULTI) == 0 &&
+		(infobits & XLHL_XMAX_LOCK_ONLY) == 0 &&
+		(infobits & XLHL_XMAX_EXCL_LOCK) == 0 &&
+		(infobits & XLHL_XMAX_KEYSHR_LOCK) == 0 &&
+		(infobits & XLHL_KEYS_UPDATED) == 0)
+		return;
+
+	appendStringInfoString(buf, ", infobits: [");
+
 	if (infobits & XLHL_XMAX_IS_MULTI)
-		appendStringInfoString(buf, "IS_MULTI ");
+		appendStringInfoString(buf, " IS_MULTI");
 	if (infobits & XLHL_XMAX_LOCK_ONLY)
-		appendStringInfoString(buf, "LOCK_ONLY ");
+		appendStringInfoString(buf, ", LOCK_ONLY");
 	if (infobits & XLHL_XMAX_EXCL_LOCK)
-		appendStringInfoString(buf, "EXCL_LOCK ");
+		appendStringInfoString(buf, ", EXCL_LOCK");
 	if (infobits & XLHL_XMAX_KEYSHR_LOCK)
-		appendStringInfoString(buf, "KEYSHR_LOCK ");
+		appendStringInfoString(buf, ", KEYSHR_LOCK");
 	if (infobits & XLHL_KEYS_UPDATED)
-		appendStringInfoString(buf, "KEYS_UPDATED ");
+		appendStringInfoString(buf, ", KEYS_UPDATED");
+
+	appendStringInfoString(buf, " ]");
+}
+
+static void
+plan_elem_desc(StringInfo buf, void *plan, void *data)
+{
+	xl_heap_freeze_plan *new_plan = (xl_heap_freeze_plan *) plan;
+	OffsetNumber **offsets = data;
+
+	appendStringInfo(buf, "{ xmax: %u, infomask: %u, infomask2: %u, ntuples: %u",
+					 new_plan->xmax, new_plan->t_infomask,
+					 new_plan->t_infomask2,
+					 new_plan->ntuples);
+
+	appendStringInfoString(buf, ", offsets:");
+	array_desc(buf, *offsets, sizeof(OffsetNumber), new_plan->ntuples,
+			   &offset_elem_desc, NULL);
+
+	*offsets += new_plan->ntuples;
+
+	appendStringInfo(buf, " }");
 }
 
 void
@@ -42,14 +74,15 @@ heap_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_insert *xlrec = (xl_heap_insert *) rec;
 
-		appendStringInfo(buf, "off %u flags 0x%02X", xlrec->offnum,
+		appendStringInfo(buf, "off: %u, flags: 0x%02X",
+						 xlrec->offnum,
 						 xlrec->flags);
 	}
 	else if (info == XLOG_HEAP_DELETE)
 	{
 		xl_heap_delete *xlrec = (xl_heap_delete *) rec;
 
-		appendStringInfo(buf, "off %u flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, flags: 0x%02X",
 						 xlrec->offnum,
 						 xlrec->flags);
 		out_infobits(buf, xlrec->infobits_set);
@@ -58,12 +91,12 @@ heap_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_update *xlrec = (xl_heap_update *) rec;
 
-		appendStringInfo(buf, "off %u xmax %u flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, xmax: %u, flags: 0x%02X",
 						 xlrec->old_offnum,
 						 xlrec->old_xmax,
 						 xlrec->flags);
 		out_infobits(buf, xlrec->old_infobits_set);
-		appendStringInfo(buf, "; new off %u xmax %u",
+		appendStringInfo(buf, ", new off: %u, xmax %u",
 						 xlrec->new_offnum,
 						 xlrec->new_xmax);
 	}
@@ -71,39 +104,41 @@ heap_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_update *xlrec = (xl_heap_update *) rec;
 
-		appendStringInfo(buf, "off %u xmax %u flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, xmax: %u, flags: 0x%02X",
 						 xlrec->old_offnum,
 						 xlrec->old_xmax,
 						 xlrec->flags);
 		out_infobits(buf, xlrec->old_infobits_set);
-		appendStringInfo(buf, "; new off %u xmax %u",
+		appendStringInfo(buf, ", new off: %u, xmax: %u",
 						 xlrec->new_offnum,
 						 xlrec->new_xmax);
 	}
 	else if (info == XLOG_HEAP_TRUNCATE)
 	{
 		xl_heap_truncate *xlrec = (xl_heap_truncate *) rec;
-		int			i;
 
+		appendStringInfoString(buf, "flags: [");
 		if (xlrec->flags & XLH_TRUNCATE_CASCADE)
-			appendStringInfoString(buf, "cascade ");
+			appendStringInfoString(buf, " CASCADE");
 		if (xlrec->flags & XLH_TRUNCATE_RESTART_SEQS)
-			appendStringInfoString(buf, "restart_seqs ");
-		appendStringInfo(buf, "nrelids %u relids", xlrec->nrelids);
-		for (i = 0; i < xlrec->nrelids; i++)
-			appendStringInfo(buf, " %u", xlrec->relids[i]);
+			appendStringInfoString(buf, ", RESTART_SEQS");
+		appendStringInfoString(buf, " ]");
+
+		appendStringInfo(buf, ", nrelids: %u", xlrec->nrelids);
+		appendStringInfoString(buf, ", relids:");
+		array_desc(buf, xlrec->relids, sizeof(Oid), xlrec->nrelids, &relid_desc, NULL);
 	}
 	else if (info == XLOG_HEAP_CONFIRM)
 	{
 		xl_heap_confirm *xlrec = (xl_heap_confirm *) rec;
 
-		appendStringInfo(buf, "off %u", xlrec->offnum);
+		appendStringInfo(buf, "off: %u", xlrec->offnum);
 	}
 	else if (info == XLOG_HEAP_LOCK)
 	{
 		xl_heap_lock *xlrec = (xl_heap_lock *) rec;
 
-		appendStringInfo(buf, "off %u: xid %u: flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, xid: %u, flags: 0x%02X",
 						 xlrec->offnum, xlrec->locking_xid, xlrec->flags);
 		out_infobits(buf, xlrec->infobits_set);
 	}
@@ -111,9 +146,10 @@ heap_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_inplace *xlrec = (xl_heap_inplace *) rec;
 
-		appendStringInfo(buf, "off %u", xlrec->offnum);
+		appendStringInfo(buf, "off: %u", xlrec->offnum);
 	}
 }
+
 void
 heap2_desc(StringInfo buf, XLogReaderState *record)
 {
@@ -125,43 +161,110 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_prune *xlrec = (xl_heap_prune *) rec;
 
-		appendStringInfo(buf, "snapshotConflictHorizon %u nredirected %u ndead %u",
+		appendStringInfo(buf, "snapshotConflictHorizon: %u, nredirected: %u, ndead: %u",
 						 xlrec->snapshotConflictHorizon,
 						 xlrec->nredirected,
 						 xlrec->ndead);
+
+
+		if (!XLogRecHasBlockImage(record, 0))
+		{
+			OffsetNumber *end;
+			OffsetNumber *redirected;
+			OffsetNumber *nowdead;
+			OffsetNumber *nowunused;
+			int			nredirected;
+			int			nunused;
+			Size		datalen;
+
+			redirected = (OffsetNumber *) XLogRecGetBlockData(record, 0,
+															  &datalen);
+
+			nredirected = xlrec->nredirected;
+			end = (OffsetNumber *) ((char *) redirected + datalen);
+			nowdead = redirected + (nredirected * 2);
+			nowunused = nowdead + xlrec->ndead;
+			nunused = (end - nowunused);
+			Assert(nunused >= 0);
+
+			appendStringInfo(buf, ", nunused: %u", nunused);
+
+			appendStringInfoString(buf, ", redirected:");
+			array_desc(buf, redirected, sizeof(OffsetNumber) * 2,
+					   nredirected * 2, &redirect_elem_desc, NULL);
+			appendStringInfoString(buf, ", dead:");
+			array_desc(buf, nowdead, sizeof(OffsetNumber), xlrec->ndead,
+					   &offset_elem_desc, NULL);
+			appendStringInfoString(buf, ", unused:");
+			array_desc(buf, nowunused, sizeof(OffsetNumber), nunused,
+					   &offset_elem_desc, NULL);
+		}
 	}
 	else if (info == XLOG_HEAP2_VACUUM)
 	{
 		xl_heap_vacuum *xlrec = (xl_heap_vacuum *) rec;
 
-		appendStringInfo(buf, "nunused %u", xlrec->nunused);
+		appendStringInfo(buf, "nunused: %u", xlrec->nunused);
+
+		if (!XLogRecHasBlockImage(record, 0))
+		{
+			OffsetNumber *nowunused;
+
+			nowunused = (OffsetNumber *) XLogRecGetBlockData(record, 0, NULL);
+
+			appendStringInfoString(buf, ", unused:");
+			array_desc(buf, nowunused, sizeof(OffsetNumber), xlrec->nunused, &offset_elem_desc, NULL);
+		}
 	}
 	else if (info == XLOG_HEAP2_FREEZE_PAGE)
 	{
 		xl_heap_freeze_page *xlrec = (xl_heap_freeze_page *) rec;
 
-		appendStringInfo(buf, "snapshotConflictHorizon %u nplans %u",
+		appendStringInfo(buf, "snapshotConflictHorizon: %u, nplans: %u",
 						 xlrec->snapshotConflictHorizon, xlrec->nplans);
+
+		if (!XLogRecHasBlockImage(record, 0))
+		{
+			xl_heap_freeze_plan *plans;
+			OffsetNumber *offsets;
+
+			plans = (xl_heap_freeze_plan *) XLogRecGetBlockData(record, 0, NULL);
+			offsets = (OffsetNumber *) &plans[xlrec->nplans];
+			appendStringInfoString(buf, ", plans:");
+			array_desc(buf,
+					   plans,
+					   sizeof(xl_heap_freeze_plan),
+					   xlrec->nplans,
+					   &plan_elem_desc,
+					   &offsets);
+
+		}
 	}
 	else if (info == XLOG_HEAP2_VISIBLE)
 	{
 		xl_heap_visible *xlrec = (xl_heap_visible *) rec;
 
-		appendStringInfo(buf, "snapshotConflictHorizon %u flags 0x%02X",
+		appendStringInfo(buf, "snapshotConflictHorizon: %u, flags: 0x%02X",
 						 xlrec->snapshotConflictHorizon, xlrec->flags);
 	}
 	else if (info == XLOG_HEAP2_MULTI_INSERT)
 	{
 		xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec;
+		bool		isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
 
-		appendStringInfo(buf, "%d tuples flags 0x%02X", xlrec->ntuples,
+		appendStringInfo(buf, "ntuples: %d, flags: 0x%02X", xlrec->ntuples,
 						 xlrec->flags);
+
+		appendStringInfoString(buf, ", offsets:");
+		if (!XLogRecHasBlockImage(record, 0) && !isinit)
+			array_desc(buf, xlrec->offsets, sizeof(OffsetNumber),
+					   xlrec->ntuples, &offset_elem_desc, NULL);
 	}
 	else if (info == XLOG_HEAP2_LOCK_UPDATED)
 	{
 		xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec;
 
-		appendStringInfo(buf, "off %u: xmax %u: flags 0x%02X ",
+		appendStringInfo(buf, "off: %u, xmax: %u, flags: 0x%02X",
 						 xlrec->offnum, xlrec->xmax, xlrec->flags);
 		out_infobits(buf, xlrec->infobits_set);
 	}
@@ -169,13 +272,13 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec;
 
-		appendStringInfo(buf, "rel %u/%u/%u; tid %u/%u",
+		appendStringInfo(buf, "rel: %u/%u/%u, tid: %u/%u",
 						 xlrec->target_locator.spcOid,
 						 xlrec->target_locator.dbOid,
 						 xlrec->target_locator.relNumber,
 						 ItemPointerGetBlockNumber(&(xlrec->target_tid)),
 						 ItemPointerGetOffsetNumber(&(xlrec->target_tid)));
-		appendStringInfo(buf, "; cmin: %u, cmax: %u, combo: %u",
+		appendStringInfo(buf, ", cmin: %u, cmax: %u, combo: %u",
 						 xlrec->cmin, xlrec->cmax, xlrec->combocid);
 	}
 }
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index 166cee67b..f76e87e2d 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -16,6 +16,7 @@ rmgr_desc_sources = files(
   'nbtdesc.c',
   'relmapdesc.c',
   'replorigindesc.c',
+  'rmgrdesc_utils.c',
   'seqdesc.c',
   'smgrdesc.c',
   'spgdesc.c',
diff --git a/src/backend/access/rmgrdesc/rmgrdesc_utils.c b/src/backend/access/rmgrdesc/rmgrdesc_utils.c
new file mode 100644
index 000000000..2b3c1887d
--- /dev/null
+++ b/src/backend/access/rmgrdesc/rmgrdesc_utils.c
@@ -0,0 +1,83 @@
+/*-------------------------------------------------------------------------
+ *
+ * rmgrdesc_utils.c
+ *		support functions for rmgrdesc
+ *
+ * Portions Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/rmgrdesc/rmgrdesc_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/rmgrdesc_utils.h"
+
+/*
+ * Guidelines for formatting desc functions:
+ *
+ * member1_name: member1_value, member2_name: member2_value
+ *
+ * If the value is a list, please use:
+ *
+ * member3_name: [ member3_list_value1, member3_list_value2 ]
+ *
+ * The first item appended to the string should not be prepended by any spaces
+ * or comma, however all subsequent appends to the string are responsible for
+ * prepending themselves with a comma followed by a space.
+ *
+ * Arrays should have a space between the opening square bracket and first
+ * element and between the last element and closing brace.
+ *
+ * Flags should be in ALL CAPS.
+ *
+ * For lists/arrays of items, the number of those items should be listed at
+ * the beginning with all of the other numbers.
+ *
+ * List punctuation should still be included even if there are 0 items.
+ *
+ * Composite objects in a list should be surrounded with { }.
+ */
+void
+array_desc(StringInfo buf, void *array, size_t elem_size, int count,
+		   void (*elem_desc) (StringInfo buf, void *elem, void *data),
+		   void *data)
+{
+	if (count == 0)
+	{
+		appendStringInfoString(buf, " []");
+		return;
+	}
+	appendStringInfo(buf, " [");
+	for (int i = 0; i < count; i++)
+	{
+		if (i > 0)
+			appendStringInfoString(buf, ",");
+		appendStringInfoString(buf, " ");
+
+		elem_desc(buf, (char *) array + elem_size * i, data);
+	}
+	appendStringInfoString(buf, " ]");
+}
+
+void
+offset_elem_desc(StringInfo buf, void *offset, void *data)
+{
+	appendStringInfo(buf, "%u", *(OffsetNumber *) offset);
+}
+
+void
+redirect_elem_desc(StringInfo buf, void *offset, void *data)
+{
+	OffsetNumber *new_offset = (OffsetNumber *) offset;
+
+	appendStringInfo(buf, "%u->%u", new_offset[0], new_offset[1]);
+}
+
+void
+relid_desc(StringInfo buf, void *relid, void *data)
+{
+	appendStringInfo(buf, "%u", *(Oid *) relid);
+}
diff --git a/src/bin/pg_waldump/Makefile b/src/bin/pg_waldump/Makefile
index d6459e17c..0ecf58203 100644
--- a/src/bin/pg_waldump/Makefile
+++ b/src/bin/pg_waldump/Makefile
@@ -18,7 +18,7 @@ OBJS = \
 
 override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 
-RMGRDESCSOURCES = $(sort $(notdir $(wildcard $(top_srcdir)/src/backend/access/rmgrdesc/*desc.c)))
+RMGRDESCSOURCES = $(sort $(notdir $(wildcard $(top_srcdir)/src/backend/access/rmgrdesc/*desc*.c)))
 RMGRDESCOBJS = $(patsubst %.c,%.o,$(RMGRDESCSOURCES))
 
 
diff --git a/doc/src/sgml/pgwalinspect.sgml b/doc/src/sgml/pgwalinspect.sgml
index d9ed8f0a9..0928717ba 100644
--- a/doc/src/sgml/pgwalinspect.sgml
+++ b/doc/src/sgml/pgwalinspect.sgml
@@ -71,19 +71,19 @@
       after the <replaceable>in_lsn</replaceable> argument.  For
       example:
 <screen>
-postgres=# SELECT * FROM pg_get_wal_record_info('0/1E826E98');
--[ RECORD 1 ]----+----------------------------------------------------
-start_lsn        | 0/1E826F20
-end_lsn          | 0/1E826F60
-prev_lsn         | 0/1E826C80
+postgres=# SELECT * FROM pg_get_wal_record_info(''0/E84F5E8'');
+-[ RECORD 1 ]----+--------------------------------------------------
+start_lsn        | 0/E84F5E8
+end_lsn          | 0/E84F620
+prev_lsn         | 0/E84F5A8
 xid              | 0
 resource_manager | Heap2
-record_type      | PRUNE
-record_length    | 58
-main_data_length | 8
+record_type      | VACUUM
+record_length    | 50
+main_data_length | 2
 fpi_length       | 0
-description      | snapshotConflictHorizon 33748 nredirected 0 ndead 2
-block_ref        | blkref #0: rel 1663/5/60221 fork main blk 2
+description      | nunused: 1, unused: [ 22 ]
+block_ref        | blkref #0: rel 1663/16389/20884 fork main blk 126
 </screen>
      </para>
      <para>
-- 
2.34.1

