#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
    {
        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);
    {
        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);
    }
    {
        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);
    }
    {
        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)
 {
    {
        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, &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);
    }
    {
        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);
    }
 }
 
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * rmgrdesc_utils.c
+ *   Support functions for rmgrdesc routines
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *   src/backend/access/rmgrdesc/rmgrdesc_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/rmgrdesc_utils.h"
+#include "storage/off.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);
+}