From 10ec7b3366f7ffdc31295831cb112863446fb578 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 25 Jan 2023 08:38:38 -0500
Subject: [PATCH v1] 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 relid arrays
---
 src/backend/access/rmgrdesc/Makefile         |   1 +
 src/backend/access/rmgrdesc/heapdesc.c       | 154 +++++++++++++++----
 src/backend/access/rmgrdesc/meson.build      |   1 +
 src/backend/access/rmgrdesc/rmgrdesc_utils.c |  88 +++++++++++
 src/bin/pg_waldump/Makefile                  |   2 +-
 src/include/access/heapam_xlog.h             |   1 +
 src/include/access/rmgrdesc_utils.h          |  33 ++++
 7 files changed, 251 insertions(+), 29 deletions(-)
 create mode 100644 src/backend/access/rmgrdesc/rmgrdesc_utils.c
 create mode 100644 src/include/access/rmgrdesc_utils.h

diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index f88d72fd86..cd95eec37f 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 628f7e8215..25a663c71b 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -15,22 +15,56 @@
 #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 *restrict plan, void *restrict 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
 heap_desc(StringInfo buf, XLogReaderState *record)
 {
@@ -42,14 +76,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 +93,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 +106,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 +148,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 +163,103 @@ 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;
+			Size		datalen;
+
+			nowunused = (OffsetNumber *) XLogRecGetBlockData(record, 0, &datalen);
+
+			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 = (xl_heap_freeze_plan *) XLogRecGetBlockData(record, 0, NULL);
+			OffsetNumber *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 +267,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 166cee67b6..f76e87e2d7 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 0000000000..fa45c796a0
--- /dev/null
+++ b/src/backend/access/rmgrdesc/rmgrdesc_utils.c
@@ -0,0 +1,88 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * The expected formatting of a desc function for an xlog record is as follows:
+ * 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
+ * prepended 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 the other numbers.
+ *
+ * whether or not to leave out a n[item] if the number is 0 (not consistent for
+ * XLOG_HEAP2_VACUUM and XLOG_HEAP2_PRUNE
+ *
+ * composite objects in a list should be surrounded with { }
+ *
+ * etc.
+ *
+ * TODO: make this description better/maybe put in a README
+ */
+
+
+void
+array_desc(StringInfo buf, void *array, size_t elem_size, int count,
+		   void (*elem_desc) (StringInfo buf, void *restrict elem, void *restrict data),
+		   void *restrict 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
+redirect_elem_desc(StringInfo buf, void *restrict offset, void *restrict data)
+{
+	OffsetNumber *new_offset = (OffsetNumber *) offset;
+
+	appendStringInfo(buf, "%u->%u", new_offset[0], new_offset[1]);
+}
+
+void
+offset_elem_desc(StringInfo buf, void *restrict offset, void *restrict data)
+{
+	appendStringInfo(buf, "%u", *(OffsetNumber *) offset);
+}
+
+void
+relid_desc(StringInfo buf, void *restrict relid, void *restrict data)
+{
+	appendStringInfo(buf, "%u", *(Oid *) relid);
+}
diff --git a/src/bin/pg_waldump/Makefile b/src/bin/pg_waldump/Makefile
index d6459e17c7..61c9de470c 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) $(top_srcdir)/src/backend/access/rmgrdesc/rmgrdesc_utils.c))
 RMGRDESCOBJS = $(patsubst %.c,%.o,$(RMGRDESCSOURCES))
 
 
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index 8cb0d8da19..e7a5179b9f 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -265,6 +265,7 @@ typedef struct xl_heap_vacuum
 #define SizeOfHeapVacuum (offsetof(xl_heap_vacuum, nunused) + sizeof(uint16))
 
 /* flags for infobits_set */
+// add a comment about this
 #define XLHL_XMAX_IS_MULTI		0x01
 #define XLHL_XMAX_LOCK_ONLY		0x02
 #define XLHL_XMAX_EXCL_LOCK		0x04
diff --git a/src/include/access/rmgrdesc_utils.h b/src/include/access/rmgrdesc_utils.h
new file mode 100644
index 0000000000..7e4fcbdf25
--- /dev/null
+++ b/src/include/access/rmgrdesc_utils.h
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+void
+			array_desc(StringInfo buf, void *array, size_t elem_size, int count,
+					   void (*elem_desc) (StringInfo buf, void *restrict elem, void *restrict data),
+					   void *restrict data);
+
+void
+			offset_elem_desc(StringInfo buf, void *restrict offset, void *restrict data);
+
+void
+			redirect_elem_desc(StringInfo buf, void *restrict offset, void *restrict data);
+
+void
+			relid_desc(StringInfo buf, void *restrict relid, void *restrict data);
+
+
+#endif							/* RMGRDESC_UTILS_H */
-- 
2.38.1

