summaryrefslogtreecommitdiffstats
path: root/src/backend/access/rmgrdesc
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/access/rmgrdesc')
-rw-r--r--src/backend/access/rmgrdesc/Makefile34
-rw-r--r--src/backend/access/rmgrdesc/brindesc.c107
-rw-r--r--src/backend/access/rmgrdesc/clogdesc.c59
-rw-r--r--src/backend/access/rmgrdesc/committsdesc.c55
-rw-r--r--src/backend/access/rmgrdesc/dbasedesc.c75
-rw-r--r--src/backend/access/rmgrdesc/genericdesc.c56
-rw-r--r--src/backend/access/rmgrdesc/gindesc.c218
-rw-r--r--src/backend/access/rmgrdesc/gistdesc.c116
-rw-r--r--src/backend/access/rmgrdesc/hashdesc.c172
-rw-r--r--src/backend/access/rmgrdesc/heapdesc.c265
-rw-r--r--src/backend/access/rmgrdesc/logicalmsgdesc.c52
-rw-r--r--src/backend/access/rmgrdesc/mxactdesc.c105
-rw-r--r--src/backend/access/rmgrdesc/nbtdesc.c178
-rw-r--r--src/backend/access/rmgrdesc/relmapdesc.c47
-rw-r--r--src/backend/access/rmgrdesc/replorigindesc.c62
-rw-r--r--src/backend/access/rmgrdesc/seqdesc.c46
-rw-r--r--src/backend/access/rmgrdesc/smgrdesc.c61
-rw-r--r--src/backend/access/rmgrdesc/spgdesc.c164
-rw-r--r--src/backend/access/rmgrdesc/standbydesc.c135
-rw-r--r--src/backend/access/rmgrdesc/tblspcdesc.c56
-rw-r--r--src/backend/access/rmgrdesc/xactdesc.c514
-rw-r--r--src/backend/access/rmgrdesc/xlogdesc.c331
22 files changed, 2908 insertions, 0 deletions
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
new file mode 100644
index 0000000..f88d72f
--- /dev/null
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -0,0 +1,34 @@
+#
+# Makefile for the rmgr descriptor routines
+#
+# src/backend/access/rmgrdesc/Makefile
+#
+
+subdir = src/backend/access/rmgrdesc
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ brindesc.o \
+ clogdesc.o \
+ committsdesc.o \
+ dbasedesc.o \
+ genericdesc.o \
+ gindesc.o \
+ gistdesc.o \
+ hashdesc.o \
+ heapdesc.o \
+ logicalmsgdesc.o \
+ mxactdesc.o \
+ nbtdesc.o \
+ relmapdesc.o \
+ replorigindesc.o \
+ seqdesc.o \
+ smgrdesc.o \
+ spgdesc.o \
+ standbydesc.o \
+ tblspcdesc.o \
+ xactdesc.o \
+ xlogdesc.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/rmgrdesc/brindesc.c b/src/backend/access/rmgrdesc/brindesc.c
new file mode 100644
index 0000000..f05607e
--- /dev/null
+++ b/src/backend/access/rmgrdesc/brindesc.c
@@ -0,0 +1,107 @@
+/*-------------------------------------------------------------------------
+ *
+ * brindesc.c
+ * rmgr descriptor routines for BRIN indexes
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/brindesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/brin_xlog.h"
+
+void
+brin_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ info &= XLOG_BRIN_OPMASK;
+ if (info == XLOG_BRIN_CREATE_INDEX)
+ {
+ xl_brin_createidx *xlrec = (xl_brin_createidx *) rec;
+
+ appendStringInfo(buf, "v%d pagesPerRange %u",
+ xlrec->version, xlrec->pagesPerRange);
+ }
+ else if (info == XLOG_BRIN_INSERT)
+ {
+ xl_brin_insert *xlrec = (xl_brin_insert *) rec;
+
+ appendStringInfo(buf, "heapBlk %u pagesPerRange %u offnum %u",
+ xlrec->heapBlk,
+ xlrec->pagesPerRange,
+ xlrec->offnum);
+ }
+ else if (info == XLOG_BRIN_UPDATE)
+ {
+ xl_brin_update *xlrec = (xl_brin_update *) rec;
+
+ appendStringInfo(buf, "heapBlk %u pagesPerRange %u old offnum %u, new offnum %u",
+ xlrec->insert.heapBlk,
+ xlrec->insert.pagesPerRange,
+ xlrec->oldOffnum,
+ xlrec->insert.offnum);
+ }
+ else if (info == XLOG_BRIN_SAMEPAGE_UPDATE)
+ {
+ xl_brin_samepage_update *xlrec = (xl_brin_samepage_update *) rec;
+
+ appendStringInfo(buf, "offnum %u", xlrec->offnum);
+ }
+ else if (info == XLOG_BRIN_REVMAP_EXTEND)
+ {
+ xl_brin_revmap_extend *xlrec = (xl_brin_revmap_extend *) rec;
+
+ appendStringInfo(buf, "targetBlk %u", xlrec->targetBlk);
+ }
+ else if (info == XLOG_BRIN_DESUMMARIZE)
+ {
+ xl_brin_desummarize *xlrec = (xl_brin_desummarize *) rec;
+
+ appendStringInfo(buf, "pagesPerRange %u, heapBlk %u, page offset %u",
+ xlrec->pagesPerRange, xlrec->heapBlk, xlrec->regOffset);
+ }
+}
+
+const char *
+brin_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_BRIN_CREATE_INDEX:
+ id = "CREATE_INDEX";
+ break;
+ case XLOG_BRIN_INSERT:
+ id = "INSERT";
+ break;
+ case XLOG_BRIN_INSERT | XLOG_BRIN_INIT_PAGE:
+ id = "INSERT+INIT";
+ break;
+ case XLOG_BRIN_UPDATE:
+ id = "UPDATE";
+ break;
+ case XLOG_BRIN_UPDATE | XLOG_BRIN_INIT_PAGE:
+ id = "UPDATE+INIT";
+ break;
+ case XLOG_BRIN_SAMEPAGE_UPDATE:
+ id = "SAMEPAGE_UPDATE";
+ break;
+ case XLOG_BRIN_REVMAP_EXTEND:
+ id = "REVMAP_EXTEND";
+ break;
+ case XLOG_BRIN_DESUMMARIZE:
+ id = "DESUMMARIZE";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/clogdesc.c b/src/backend/access/rmgrdesc/clogdesc.c
new file mode 100644
index 0000000..8751373
--- /dev/null
+++ b/src/backend/access/rmgrdesc/clogdesc.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * clogdesc.c
+ * rmgr descriptor routines for access/transam/clog.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/clogdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/clog.h"
+
+
+void
+clog_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == CLOG_ZEROPAGE)
+ {
+ int pageno;
+
+ memcpy(&pageno, rec, sizeof(int));
+ appendStringInfo(buf, "page %d", pageno);
+ }
+ else if (info == CLOG_TRUNCATE)
+ {
+ xl_clog_truncate xlrec;
+
+ memcpy(&xlrec, rec, sizeof(xl_clog_truncate));
+ appendStringInfo(buf, "page %d; oldestXact %u",
+ xlrec.pageno, xlrec.oldestXact);
+ }
+}
+
+const char *
+clog_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case CLOG_ZEROPAGE:
+ id = "ZEROPAGE";
+ break;
+ case CLOG_TRUNCATE:
+ id = "TRUNCATE";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/committsdesc.c b/src/backend/access/rmgrdesc/committsdesc.c
new file mode 100644
index 0000000..3a65538
--- /dev/null
+++ b/src/backend/access/rmgrdesc/committsdesc.c
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * committsdesc.c
+ * rmgr descriptor routines for access/transam/commit_ts.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/committsdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/commit_ts.h"
+#include "utils/timestamp.h"
+
+
+void
+commit_ts_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == COMMIT_TS_ZEROPAGE)
+ {
+ int pageno;
+
+ memcpy(&pageno, rec, sizeof(int));
+ appendStringInfo(buf, "%d", pageno);
+ }
+ else if (info == COMMIT_TS_TRUNCATE)
+ {
+ xl_commit_ts_truncate *trunc = (xl_commit_ts_truncate *) rec;
+
+ appendStringInfo(buf, "pageno %d, oldestXid %u",
+ trunc->pageno, trunc->oldestXid);
+ }
+}
+
+const char *
+commit_ts_identify(uint8 info)
+{
+ switch (info)
+ {
+ case COMMIT_TS_ZEROPAGE:
+ return "ZEROPAGE";
+ case COMMIT_TS_TRUNCATE:
+ return "TRUNCATE";
+ default:
+ return NULL;
+ }
+}
diff --git a/src/backend/access/rmgrdesc/dbasedesc.c b/src/backend/access/rmgrdesc/dbasedesc.c
new file mode 100644
index 0000000..523d0b3
--- /dev/null
+++ b/src/backend/access/rmgrdesc/dbasedesc.c
@@ -0,0 +1,75 @@
+/*-------------------------------------------------------------------------
+ *
+ * dbasedesc.c
+ * rmgr descriptor routines for commands/dbcommands.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/dbasedesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "commands/dbcommands_xlog.h"
+#include "lib/stringinfo.h"
+
+
+void
+dbase_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_DBASE_CREATE_FILE_COPY)
+ {
+ xl_dbase_create_file_copy_rec *xlrec =
+ (xl_dbase_create_file_copy_rec *) rec;
+
+ appendStringInfo(buf, "copy dir %u/%u to %u/%u",
+ xlrec->src_tablespace_id, xlrec->src_db_id,
+ xlrec->tablespace_id, xlrec->db_id);
+ }
+ else if (info == XLOG_DBASE_CREATE_WAL_LOG)
+ {
+ xl_dbase_create_wal_log_rec *xlrec =
+ (xl_dbase_create_wal_log_rec *) rec;
+
+ appendStringInfo(buf, "create dir %u/%u",
+ xlrec->tablespace_id, xlrec->db_id);
+ }
+ else if (info == XLOG_DBASE_DROP)
+ {
+ xl_dbase_drop_rec *xlrec = (xl_dbase_drop_rec *) rec;
+ int i;
+
+ appendStringInfoString(buf, "dir");
+ for (i = 0; i < xlrec->ntablespaces; i++)
+ appendStringInfo(buf, " %u/%u",
+ xlrec->tablespace_ids[i], xlrec->db_id);
+ }
+}
+
+const char *
+dbase_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_DBASE_CREATE_FILE_COPY:
+ id = "CREATE_FILE_COPY";
+ break;
+ case XLOG_DBASE_CREATE_WAL_LOG:
+ id = "CREATE_WAL_LOG";
+ break;
+ case XLOG_DBASE_DROP:
+ id = "DROP";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/genericdesc.c b/src/backend/access/rmgrdesc/genericdesc.c
new file mode 100644
index 0000000..877beb5
--- /dev/null
+++ b/src/backend/access/rmgrdesc/genericdesc.c
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------
+ *
+ * genericdesc.c
+ * rmgr descriptor routines for access/transam/generic_xlog.c
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/access/rmgrdesc/genericdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/generic_xlog.h"
+#include "lib/stringinfo.h"
+#include "storage/relfilenode.h"
+
+/*
+ * Description of generic xlog record: write page regions that this record
+ * overrides.
+ */
+void
+generic_desc(StringInfo buf, XLogReaderState *record)
+{
+ Pointer ptr = XLogRecGetData(record),
+ end = ptr + XLogRecGetDataLen(record);
+
+ while (ptr < end)
+ {
+ OffsetNumber offset,
+ length;
+
+ memcpy(&offset, ptr, sizeof(offset));
+ ptr += sizeof(offset);
+ memcpy(&length, ptr, sizeof(length));
+ ptr += sizeof(length);
+ ptr += length;
+
+ if (ptr < end)
+ appendStringInfo(buf, "offset %u, length %u; ", offset, length);
+ else
+ appendStringInfo(buf, "offset %u, length %u", offset, length);
+ }
+}
+
+/*
+ * Identification of generic xlog record: we don't distinguish any subtypes
+ * inside generic xlog records.
+ */
+const char *
+generic_identify(uint8 info)
+{
+ return "Generic";
+}
diff --git a/src/backend/access/rmgrdesc/gindesc.c b/src/backend/access/rmgrdesc/gindesc.c
new file mode 100644
index 0000000..57f7bce
--- /dev/null
+++ b/src/backend/access/rmgrdesc/gindesc.c
@@ -0,0 +1,218 @@
+/*-------------------------------------------------------------------------
+ *
+ * gindesc.c
+ * rmgr descriptor routines for access/transam/gin/ginxlog.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/gindesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/ginxlog.h"
+#include "access/xlogutils.h"
+#include "lib/stringinfo.h"
+#include "storage/relfilenode.h"
+
+static void
+desc_recompress_leaf(StringInfo buf, ginxlogRecompressDataLeaf *insertData)
+{
+ int i;
+ char *walbuf = ((char *) insertData) + sizeof(ginxlogRecompressDataLeaf);
+
+ appendStringInfo(buf, " %d segments:", (int) insertData->nactions);
+
+ for (i = 0; i < insertData->nactions; i++)
+ {
+ uint8 a_segno = *((uint8 *) (walbuf++));
+ uint8 a_action = *((uint8 *) (walbuf++));
+ uint16 nitems = 0;
+ int newsegsize = 0;
+
+ if (a_action == GIN_SEGMENT_INSERT ||
+ a_action == GIN_SEGMENT_REPLACE)
+ {
+ newsegsize = SizeOfGinPostingList((GinPostingList *) walbuf);
+ walbuf += SHORTALIGN(newsegsize);
+ }
+
+ if (a_action == GIN_SEGMENT_ADDITEMS)
+ {
+ memcpy(&nitems, walbuf, sizeof(uint16));
+ walbuf += sizeof(uint16);
+ walbuf += nitems * sizeof(ItemPointerData);
+ }
+
+ switch (a_action)
+ {
+ case GIN_SEGMENT_ADDITEMS:
+ appendStringInfo(buf, " %d (add %d items)", a_segno, nitems);
+ break;
+ case GIN_SEGMENT_DELETE:
+ appendStringInfo(buf, " %d (delete)", a_segno);
+ break;
+ case GIN_SEGMENT_INSERT:
+ appendStringInfo(buf, " %d (insert)", a_segno);
+ break;
+ case GIN_SEGMENT_REPLACE:
+ appendStringInfo(buf, " %d (replace)", a_segno);
+ break;
+ default:
+ appendStringInfo(buf, " %d unknown action %d ???", a_segno, a_action);
+ /* cannot decode unrecognized actions further */
+ return;
+ }
+ }
+}
+
+void
+gin_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ switch (info)
+ {
+ case XLOG_GIN_CREATE_PTREE:
+ /* no further information */
+ break;
+ case XLOG_GIN_INSERT:
+ {
+ ginxlogInsert *xlrec = (ginxlogInsert *) rec;
+
+ appendStringInfo(buf, "isdata: %c isleaf: %c",
+ (xlrec->flags & GIN_INSERT_ISDATA) ? 'T' : 'F',
+ (xlrec->flags & GIN_INSERT_ISLEAF) ? 'T' : 'F');
+ if (!(xlrec->flags & GIN_INSERT_ISLEAF))
+ {
+ char *payload = rec + sizeof(ginxlogInsert);
+ BlockNumber leftChildBlkno;
+ BlockNumber rightChildBlkno;
+
+ leftChildBlkno = BlockIdGetBlockNumber((BlockId) payload);
+ payload += sizeof(BlockIdData);
+ rightChildBlkno = BlockIdGetBlockNumber((BlockId) payload);
+ payload += sizeof(BlockNumber);
+ appendStringInfo(buf, " children: %u/%u",
+ leftChildBlkno, rightChildBlkno);
+ }
+ if (XLogRecHasBlockImage(record, 0))
+ {
+ if (XLogRecBlockImageApply(record, 0))
+ appendStringInfoString(buf, " (full page image)");
+ else
+ appendStringInfoString(buf, " (full page image, for WAL verification)");
+ }
+ else
+ {
+ char *payload = XLogRecGetBlockData(record, 0, NULL);
+
+ if (!(xlrec->flags & GIN_INSERT_ISDATA))
+ appendStringInfo(buf, " isdelete: %c",
+ (((ginxlogInsertEntry *) payload)->isDelete) ? 'T' : 'F');
+ else if (xlrec->flags & GIN_INSERT_ISLEAF)
+ desc_recompress_leaf(buf, (ginxlogRecompressDataLeaf *) payload);
+ else
+ {
+ ginxlogInsertDataInternal *insertData =
+ (ginxlogInsertDataInternal *) payload;
+
+ appendStringInfo(buf, " pitem: %u-%u/%u",
+ PostingItemGetBlockNumber(&insertData->newitem),
+ ItemPointerGetBlockNumber(&insertData->newitem.key),
+ ItemPointerGetOffsetNumber(&insertData->newitem.key));
+ }
+ }
+ }
+ break;
+ case XLOG_GIN_SPLIT:
+ {
+ ginxlogSplit *xlrec = (ginxlogSplit *) rec;
+
+ appendStringInfo(buf, "isrootsplit: %c",
+ (((ginxlogSplit *) rec)->flags & GIN_SPLIT_ROOT) ? 'T' : 'F');
+ appendStringInfo(buf, " isdata: %c isleaf: %c",
+ (xlrec->flags & GIN_INSERT_ISDATA) ? 'T' : 'F',
+ (xlrec->flags & GIN_INSERT_ISLEAF) ? 'T' : 'F');
+ }
+ break;
+ case XLOG_GIN_VACUUM_PAGE:
+ /* no further information */
+ break;
+ case XLOG_GIN_VACUUM_DATA_LEAF_PAGE:
+ {
+ if (XLogRecHasBlockImage(record, 0))
+ {
+ if (XLogRecBlockImageApply(record, 0))
+ appendStringInfoString(buf, " (full page image)");
+ else
+ appendStringInfoString(buf, " (full page image, for WAL verification)");
+ }
+ else
+ {
+ ginxlogVacuumDataLeafPage *xlrec =
+ (ginxlogVacuumDataLeafPage *) XLogRecGetBlockData(record, 0, NULL);
+
+ desc_recompress_leaf(buf, &xlrec->data);
+ }
+ }
+ break;
+ case XLOG_GIN_DELETE_PAGE:
+ /* no further information */
+ break;
+ case XLOG_GIN_UPDATE_META_PAGE:
+ /* no further information */
+ break;
+ case XLOG_GIN_INSERT_LISTPAGE:
+ /* no further information */
+ break;
+ case XLOG_GIN_DELETE_LISTPAGE:
+ appendStringInfo(buf, "ndeleted: %d",
+ ((ginxlogDeleteListPages *) rec)->ndeleted);
+ break;
+ }
+}
+
+const char *
+gin_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_GIN_CREATE_PTREE:
+ id = "CREATE_PTREE";
+ break;
+ case XLOG_GIN_INSERT:
+ id = "INSERT";
+ break;
+ case XLOG_GIN_SPLIT:
+ id = "SPLIT";
+ break;
+ case XLOG_GIN_VACUUM_PAGE:
+ id = "VACUUM_PAGE";
+ break;
+ case XLOG_GIN_VACUUM_DATA_LEAF_PAGE:
+ id = "VACUUM_DATA_LEAF_PAGE";
+ break;
+ case XLOG_GIN_DELETE_PAGE:
+ id = "DELETE_PAGE";
+ break;
+ case XLOG_GIN_UPDATE_META_PAGE:
+ id = "UPDATE_META_PAGE";
+ break;
+ case XLOG_GIN_INSERT_LISTPAGE:
+ id = "INSERT_LISTPAGE";
+ break;
+ case XLOG_GIN_DELETE_LISTPAGE:
+ id = "DELETE_LISTPAGE";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/gistdesc.c b/src/backend/access/rmgrdesc/gistdesc.c
new file mode 100644
index 0000000..d0c8e24
--- /dev/null
+++ b/src/backend/access/rmgrdesc/gistdesc.c
@@ -0,0 +1,116 @@
+/*-------------------------------------------------------------------------
+ *
+ * gistdesc.c
+ * rmgr descriptor routines for access/gist/gistxlog.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/gistdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/gistxlog.h"
+#include "lib/stringinfo.h"
+#include "storage/relfilenode.h"
+
+static void
+out_gistxlogPageUpdate(StringInfo buf, gistxlogPageUpdate *xlrec)
+{
+}
+
+static void
+out_gistxlogPageReuse(StringInfo buf, gistxlogPageReuse *xlrec)
+{
+ appendStringInfo(buf, "rel %u/%u/%u; blk %u; latestRemovedXid %u:%u",
+ xlrec->node.spcNode, xlrec->node.dbNode,
+ xlrec->node.relNode, xlrec->block,
+ EpochFromFullTransactionId(xlrec->latestRemovedFullXid),
+ XidFromFullTransactionId(xlrec->latestRemovedFullXid));
+}
+
+static void
+out_gistxlogDelete(StringInfo buf, gistxlogDelete *xlrec)
+{
+ appendStringInfo(buf, "delete: latestRemovedXid %u, nitems: %u",
+ xlrec->latestRemovedXid, xlrec->ntodelete);
+}
+
+static void
+out_gistxlogPageSplit(StringInfo buf, gistxlogPageSplit *xlrec)
+{
+ appendStringInfo(buf, "page_split: splits to %d pages",
+ xlrec->npage);
+}
+
+static void
+out_gistxlogPageDelete(StringInfo buf, gistxlogPageDelete *xlrec)
+{
+ appendStringInfo(buf, "deleteXid %u:%u; downlink %u",
+ EpochFromFullTransactionId(xlrec->deleteXid),
+ XidFromFullTransactionId(xlrec->deleteXid),
+ xlrec->downlinkOffset);
+}
+
+void
+gist_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ switch (info)
+ {
+ case XLOG_GIST_PAGE_UPDATE:
+ out_gistxlogPageUpdate(buf, (gistxlogPageUpdate *) rec);
+ break;
+ case XLOG_GIST_PAGE_REUSE:
+ out_gistxlogPageReuse(buf, (gistxlogPageReuse *) rec);
+ break;
+ case XLOG_GIST_DELETE:
+ out_gistxlogDelete(buf, (gistxlogDelete *) rec);
+ break;
+ case XLOG_GIST_PAGE_SPLIT:
+ out_gistxlogPageSplit(buf, (gistxlogPageSplit *) rec);
+ break;
+ case XLOG_GIST_PAGE_DELETE:
+ out_gistxlogPageDelete(buf, (gistxlogPageDelete *) rec);
+ break;
+ case XLOG_GIST_ASSIGN_LSN:
+ /* No details to write out */
+ break;
+ }
+}
+
+const char *
+gist_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_GIST_PAGE_UPDATE:
+ id = "PAGE_UPDATE";
+ break;
+ case XLOG_GIST_DELETE:
+ id = "DELETE";
+ break;
+ case XLOG_GIST_PAGE_REUSE:
+ id = "PAGE_REUSE";
+ break;
+ case XLOG_GIST_PAGE_SPLIT:
+ id = "PAGE_SPLIT";
+ break;
+ case XLOG_GIST_PAGE_DELETE:
+ id = "PAGE_DELETE";
+ break;
+ case XLOG_GIST_ASSIGN_LSN:
+ id = "ASSIGN_LSN";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/hashdesc.c b/src/backend/access/rmgrdesc/hashdesc.c
new file mode 100644
index 0000000..ef443bd
--- /dev/null
+++ b/src/backend/access/rmgrdesc/hashdesc.c
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ *
+ * hashdesc.c
+ * rmgr descriptor routines for access/hash/hash.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/hashdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/hash_xlog.h"
+
+void
+hash_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ switch (info)
+ {
+ case XLOG_HASH_INIT_META_PAGE:
+ {
+ xl_hash_init_meta_page *xlrec = (xl_hash_init_meta_page *) rec;
+
+ appendStringInfo(buf, "num_tuples %g, fillfactor %d",
+ xlrec->num_tuples, xlrec->ffactor);
+ break;
+ }
+ case XLOG_HASH_INIT_BITMAP_PAGE:
+ {
+ xl_hash_init_bitmap_page *xlrec = (xl_hash_init_bitmap_page *) rec;
+
+ appendStringInfo(buf, "bmsize %d", xlrec->bmsize);
+ break;
+ }
+ case XLOG_HASH_INSERT:
+ {
+ xl_hash_insert *xlrec = (xl_hash_insert *) rec;
+
+ appendStringInfo(buf, "off %u", xlrec->offnum);
+ break;
+ }
+ case XLOG_HASH_ADD_OVFL_PAGE:
+ {
+ xl_hash_add_ovfl_page *xlrec = (xl_hash_add_ovfl_page *) rec;
+
+ appendStringInfo(buf, "bmsize %d, bmpage_found %c",
+ xlrec->bmsize, (xlrec->bmpage_found) ? 'T' : 'F');
+ break;
+ }
+ case XLOG_HASH_SPLIT_ALLOCATE_PAGE:
+ {
+ xl_hash_split_allocate_page *xlrec = (xl_hash_split_allocate_page *) rec;
+
+ appendStringInfo(buf, "new_bucket %u, meta_page_masks_updated %c, issplitpoint_changed %c",
+ xlrec->new_bucket,
+ (xlrec->flags & XLH_SPLIT_META_UPDATE_MASKS) ? 'T' : 'F',
+ (xlrec->flags & XLH_SPLIT_META_UPDATE_SPLITPOINT) ? 'T' : 'F');
+ break;
+ }
+ case XLOG_HASH_SPLIT_COMPLETE:
+ {
+ xl_hash_split_complete *xlrec = (xl_hash_split_complete *) rec;
+
+ appendStringInfo(buf, "old_bucket_flag %u, new_bucket_flag %u",
+ xlrec->old_bucket_flag, xlrec->new_bucket_flag);
+ break;
+ }
+ case XLOG_HASH_MOVE_PAGE_CONTENTS:
+ {
+ xl_hash_move_page_contents *xlrec = (xl_hash_move_page_contents *) rec;
+
+ appendStringInfo(buf, "ntups %d, is_primary %c",
+ xlrec->ntups,
+ xlrec->is_prim_bucket_same_wrt ? 'T' : 'F');
+ break;
+ }
+ case XLOG_HASH_SQUEEZE_PAGE:
+ {
+ xl_hash_squeeze_page *xlrec = (xl_hash_squeeze_page *) rec;
+
+ appendStringInfo(buf, "prevblkno %u, nextblkno %u, ntups %d, is_primary %c",
+ xlrec->prevblkno,
+ xlrec->nextblkno,
+ xlrec->ntups,
+ xlrec->is_prim_bucket_same_wrt ? 'T' : 'F');
+ break;
+ }
+ case XLOG_HASH_DELETE:
+ {
+ xl_hash_delete *xlrec = (xl_hash_delete *) rec;
+
+ appendStringInfo(buf, "clear_dead_marking %c, is_primary %c",
+ xlrec->clear_dead_marking ? 'T' : 'F',
+ xlrec->is_primary_bucket_page ? 'T' : 'F');
+ break;
+ }
+ case XLOG_HASH_UPDATE_META_PAGE:
+ {
+ xl_hash_update_meta_page *xlrec = (xl_hash_update_meta_page *) rec;
+
+ appendStringInfo(buf, "ntuples %g",
+ xlrec->ntuples);
+ break;
+ }
+ case XLOG_HASH_VACUUM_ONE_PAGE:
+ {
+ xl_hash_vacuum_one_page *xlrec = (xl_hash_vacuum_one_page *) rec;
+
+ appendStringInfo(buf, "ntuples %d, latestRemovedXid %u",
+ xlrec->ntuples,
+ xlrec->latestRemovedXid);
+ break;
+ }
+ }
+}
+
+const char *
+hash_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_HASH_INIT_META_PAGE:
+ id = "INIT_META_PAGE";
+ break;
+ case XLOG_HASH_INIT_BITMAP_PAGE:
+ id = "INIT_BITMAP_PAGE";
+ break;
+ case XLOG_HASH_INSERT:
+ id = "INSERT";
+ break;
+ case XLOG_HASH_ADD_OVFL_PAGE:
+ id = "ADD_OVFL_PAGE";
+ break;
+ case XLOG_HASH_SPLIT_ALLOCATE_PAGE:
+ id = "SPLIT_ALLOCATE_PAGE";
+ break;
+ case XLOG_HASH_SPLIT_PAGE:
+ id = "SPLIT_PAGE";
+ break;
+ case XLOG_HASH_SPLIT_COMPLETE:
+ id = "SPLIT_COMPLETE";
+ break;
+ case XLOG_HASH_MOVE_PAGE_CONTENTS:
+ id = "MOVE_PAGE_CONTENTS";
+ break;
+ case XLOG_HASH_SQUEEZE_PAGE:
+ id = "SQUEEZE_PAGE";
+ break;
+ case XLOG_HASH_DELETE:
+ id = "DELETE";
+ break;
+ case XLOG_HASH_SPLIT_CLEANUP:
+ id = "SPLIT_CLEANUP";
+ break;
+ case XLOG_HASH_UPDATE_META_PAGE:
+ id = "UPDATE_META_PAGE";
+ break;
+ case XLOG_HASH_VACUUM_ONE_PAGE:
+ id = "VACUUM_ONE_PAGE";
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
new file mode 100644
index 0000000..6238085
--- /dev/null
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -0,0 +1,265 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapdesc.c
+ * rmgr descriptor routines for access/heap/heapam.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/heapdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam_xlog.h"
+
+static void
+out_infobits(StringInfo buf, uint8 infobits)
+{
+ if (infobits & XLHL_XMAX_IS_MULTI)
+ appendStringInfoString(buf, "IS_MULTI ");
+ if (infobits & XLHL_XMAX_LOCK_ONLY)
+ appendStringInfoString(buf, "LOCK_ONLY ");
+ if (infobits & XLHL_XMAX_EXCL_LOCK)
+ appendStringInfoString(buf, "EXCL_LOCK ");
+ if (infobits & XLHL_XMAX_KEYSHR_LOCK)
+ appendStringInfoString(buf, "KEYSHR_LOCK ");
+ if (infobits & XLHL_KEYS_UPDATED)
+ appendStringInfoString(buf, "KEYS_UPDATED ");
+}
+
+void
+heap_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ info &= XLOG_HEAP_OPMASK;
+ if (info == XLOG_HEAP_INSERT)
+ {
+ xl_heap_insert *xlrec = (xl_heap_insert *) rec;
+
+ 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 ",
+ xlrec->offnum,
+ xlrec->flags);
+ out_infobits(buf, xlrec->infobits_set);
+ }
+ else if (info == XLOG_HEAP_UPDATE)
+ {
+ xl_heap_update *xlrec = (xl_heap_update *) rec;
+
+ 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",
+ xlrec->new_offnum,
+ xlrec->new_xmax);
+ }
+ else if (info == XLOG_HEAP_HOT_UPDATE)
+ {
+ xl_heap_update *xlrec = (xl_heap_update *) rec;
+
+ 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",
+ xlrec->new_offnum,
+ xlrec->new_xmax);
+ }
+ else if (info == XLOG_HEAP_TRUNCATE)
+ {
+ xl_heap_truncate *xlrec = (xl_heap_truncate *) rec;
+ int i;
+
+ if (xlrec->flags & XLH_TRUNCATE_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]);
+ }
+ else if (info == XLOG_HEAP_CONFIRM)
+ {
+ xl_heap_confirm *xlrec = (xl_heap_confirm *) rec;
+
+ 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 ",
+ xlrec->offnum, xlrec->locking_xid, xlrec->flags);
+ out_infobits(buf, xlrec->infobits_set);
+ }
+ else if (info == XLOG_HEAP_INPLACE)
+ {
+ xl_heap_inplace *xlrec = (xl_heap_inplace *) rec;
+
+ appendStringInfo(buf, "off %u", xlrec->offnum);
+ }
+}
+void
+heap2_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ info &= XLOG_HEAP_OPMASK;
+ if (info == XLOG_HEAP2_PRUNE)
+ {
+ xl_heap_prune *xlrec = (xl_heap_prune *) rec;
+
+ appendStringInfo(buf, "latestRemovedXid %u nredirected %u ndead %u",
+ xlrec->latestRemovedXid,
+ xlrec->nredirected,
+ xlrec->ndead);
+ }
+ else if (info == XLOG_HEAP2_VACUUM)
+ {
+ xl_heap_vacuum *xlrec = (xl_heap_vacuum *) rec;
+
+ appendStringInfo(buf, "nunused %u", xlrec->nunused);
+ }
+ else if (info == XLOG_HEAP2_FREEZE_PAGE)
+ {
+ xl_heap_freeze_page *xlrec = (xl_heap_freeze_page *) rec;
+
+ appendStringInfo(buf, "cutoff xid %u ntuples %u",
+ xlrec->cutoff_xid, xlrec->ntuples);
+ }
+ else if (info == XLOG_HEAP2_VISIBLE)
+ {
+ xl_heap_visible *xlrec = (xl_heap_visible *) rec;
+
+ appendStringInfo(buf, "cutoff xid %u flags 0x%02X",
+ xlrec->cutoff_xid, xlrec->flags);
+ }
+ else if (info == XLOG_HEAP2_MULTI_INSERT)
+ {
+ xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec;
+
+ appendStringInfo(buf, "%d tuples flags 0x%02X", xlrec->ntuples,
+ xlrec->flags);
+ }
+ 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 ",
+ xlrec->offnum, xlrec->xmax, xlrec->flags);
+ out_infobits(buf, xlrec->infobits_set);
+ }
+ else if (info == XLOG_HEAP2_NEW_CID)
+ {
+ xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec;
+
+ appendStringInfo(buf, "rel %u/%u/%u; tid %u/%u",
+ xlrec->target_node.spcNode,
+ xlrec->target_node.dbNode,
+ xlrec->target_node.relNode,
+ ItemPointerGetBlockNumber(&(xlrec->target_tid)),
+ ItemPointerGetOffsetNumber(&(xlrec->target_tid)));
+ appendStringInfo(buf, "; cmin: %u, cmax: %u, combo: %u",
+ xlrec->cmin, xlrec->cmax, xlrec->combocid);
+ }
+}
+
+const char *
+heap_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_HEAP_INSERT:
+ id = "INSERT";
+ break;
+ case XLOG_HEAP_INSERT | XLOG_HEAP_INIT_PAGE:
+ id = "INSERT+INIT";
+ break;
+ case XLOG_HEAP_DELETE:
+ id = "DELETE";
+ break;
+ case XLOG_HEAP_UPDATE:
+ id = "UPDATE";
+ break;
+ case XLOG_HEAP_UPDATE | XLOG_HEAP_INIT_PAGE:
+ id = "UPDATE+INIT";
+ break;
+ case XLOG_HEAP_HOT_UPDATE:
+ id = "HOT_UPDATE";
+ break;
+ case XLOG_HEAP_HOT_UPDATE | XLOG_HEAP_INIT_PAGE:
+ id = "HOT_UPDATE+INIT";
+ break;
+ case XLOG_HEAP_TRUNCATE:
+ id = "TRUNCATE";
+ break;
+ case XLOG_HEAP_CONFIRM:
+ id = "HEAP_CONFIRM";
+ break;
+ case XLOG_HEAP_LOCK:
+ id = "LOCK";
+ break;
+ case XLOG_HEAP_INPLACE:
+ id = "INPLACE";
+ break;
+ }
+
+ return id;
+}
+
+const char *
+heap2_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_HEAP2_PRUNE:
+ id = "PRUNE";
+ break;
+ case XLOG_HEAP2_VACUUM:
+ id = "VACUUM";
+ break;
+ case XLOG_HEAP2_FREEZE_PAGE:
+ id = "FREEZE_PAGE";
+ break;
+ case XLOG_HEAP2_VISIBLE:
+ id = "VISIBLE";
+ break;
+ case XLOG_HEAP2_MULTI_INSERT:
+ id = "MULTI_INSERT";
+ break;
+ case XLOG_HEAP2_MULTI_INSERT | XLOG_HEAP_INIT_PAGE:
+ id = "MULTI_INSERT+INIT";
+ break;
+ case XLOG_HEAP2_LOCK_UPDATED:
+ id = "LOCK_UPDATED";
+ break;
+ case XLOG_HEAP2_NEW_CID:
+ id = "NEW_CID";
+ break;
+ case XLOG_HEAP2_REWRITE:
+ id = "REWRITE";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/logicalmsgdesc.c b/src/backend/access/rmgrdesc/logicalmsgdesc.c
new file mode 100644
index 0000000..08e03aa
--- /dev/null
+++ b/src/backend/access/rmgrdesc/logicalmsgdesc.c
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * logicalmsgdesc.c
+ * rmgr descriptor routines for replication/logical/message.c
+ *
+ * Portions Copyright (c) 2015-2022, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/logicalmsgdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "replication/message.h"
+
+void
+logicalmsg_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_LOGICAL_MESSAGE)
+ {
+ xl_logical_message *xlrec = (xl_logical_message *) rec;
+ char *prefix = xlrec->message;
+ char *message = xlrec->message + xlrec->prefix_size;
+ char *sep = "";
+
+ Assert(prefix[xlrec->prefix_size - 1] == '\0');
+
+ appendStringInfo(buf, "%s, prefix \"%s\"; payload (%zu bytes): ",
+ xlrec->transactional ? "transactional" : "non-transactional",
+ prefix, xlrec->message_size);
+ /* Write message payload as a series of hex bytes */
+ for (int cnt = 0; cnt < xlrec->message_size; cnt++)
+ {
+ appendStringInfo(buf, "%s%02X", sep, (unsigned char) message[cnt]);
+ sep = " ";
+ }
+ }
+}
+
+const char *
+logicalmsg_identify(uint8 info)
+{
+ if ((info & ~XLR_INFO_MASK) == XLOG_LOGICAL_MESSAGE)
+ return "MESSAGE";
+
+ return NULL;
+}
diff --git a/src/backend/access/rmgrdesc/mxactdesc.c b/src/backend/access/rmgrdesc/mxactdesc.c
new file mode 100644
index 0000000..7076be2
--- /dev/null
+++ b/src/backend/access/rmgrdesc/mxactdesc.c
@@ -0,0 +1,105 @@
+/*-------------------------------------------------------------------------
+ *
+ * mxactdesc.c
+ * rmgr descriptor routines for access/transam/multixact.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/mxactdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/multixact.h"
+
+static void
+out_member(StringInfo buf, MultiXactMember *member)
+{
+ appendStringInfo(buf, "%u ", member->xid);
+ switch (member->status)
+ {
+ case MultiXactStatusForKeyShare:
+ appendStringInfoString(buf, "(keysh) ");
+ break;
+ case MultiXactStatusForShare:
+ appendStringInfoString(buf, "(sh) ");
+ break;
+ case MultiXactStatusForNoKeyUpdate:
+ appendStringInfoString(buf, "(fornokeyupd) ");
+ break;
+ case MultiXactStatusForUpdate:
+ appendStringInfoString(buf, "(forupd) ");
+ break;
+ case MultiXactStatusNoKeyUpdate:
+ appendStringInfoString(buf, "(nokeyupd) ");
+ break;
+ case MultiXactStatusUpdate:
+ appendStringInfoString(buf, "(upd) ");
+ break;
+ default:
+ appendStringInfoString(buf, "(unk) ");
+ break;
+ }
+}
+
+void
+multixact_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_MULTIXACT_ZERO_OFF_PAGE ||
+ info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
+ {
+ int pageno;
+
+ memcpy(&pageno, rec, sizeof(int));
+ appendStringInfo(buf, "%d", pageno);
+ }
+ else if (info == XLOG_MULTIXACT_CREATE_ID)
+ {
+ xl_multixact_create *xlrec = (xl_multixact_create *) rec;
+ int i;
+
+ appendStringInfo(buf, "%u offset %u nmembers %d: ", xlrec->mid,
+ xlrec->moff, xlrec->nmembers);
+ for (i = 0; i < xlrec->nmembers; i++)
+ out_member(buf, &xlrec->members[i]);
+ }
+ else if (info == XLOG_MULTIXACT_TRUNCATE_ID)
+ {
+ xl_multixact_truncate *xlrec = (xl_multixact_truncate *) rec;
+
+ appendStringInfo(buf, "offsets [%u, %u), members [%u, %u)",
+ xlrec->startTruncOff, xlrec->endTruncOff,
+ xlrec->startTruncMemb, xlrec->endTruncMemb);
+ }
+}
+
+const char *
+multixact_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_MULTIXACT_ZERO_OFF_PAGE:
+ id = "ZERO_OFF_PAGE";
+ break;
+ case XLOG_MULTIXACT_ZERO_MEM_PAGE:
+ id = "ZERO_MEM_PAGE";
+ break;
+ case XLOG_MULTIXACT_CREATE_ID:
+ id = "CREATE_ID";
+ break;
+ case XLOG_MULTIXACT_TRUNCATE_ID:
+ id = "TRUNCATE_ID";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/nbtdesc.c b/src/backend/access/rmgrdesc/nbtdesc.c
new file mode 100644
index 0000000..dfbbf4e
--- /dev/null
+++ b/src/backend/access/rmgrdesc/nbtdesc.c
@@ -0,0 +1,178 @@
+/*-------------------------------------------------------------------------
+ *
+ * nbtdesc.c
+ * rmgr descriptor routines for access/nbtree/nbtxlog.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/nbtdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/nbtxlog.h"
+
+void
+btree_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ switch (info)
+ {
+ case XLOG_BTREE_INSERT_LEAF:
+ case XLOG_BTREE_INSERT_UPPER:
+ case XLOG_BTREE_INSERT_META:
+ case XLOG_BTREE_INSERT_POST:
+ {
+ xl_btree_insert *xlrec = (xl_btree_insert *) rec;
+
+ appendStringInfo(buf, "off %u", xlrec->offnum);
+ break;
+ }
+ case XLOG_BTREE_SPLIT_L:
+ case XLOG_BTREE_SPLIT_R:
+ {
+ xl_btree_split *xlrec = (xl_btree_split *) rec;
+
+ appendStringInfo(buf, "level %u, firstrightoff %d, newitemoff %d, postingoff %d",
+ xlrec->level, xlrec->firstrightoff,
+ xlrec->newitemoff, xlrec->postingoff);
+ break;
+ }
+ case XLOG_BTREE_DEDUP:
+ {
+ xl_btree_dedup *xlrec = (xl_btree_dedup *) rec;
+
+ appendStringInfo(buf, "nintervals %u", xlrec->nintervals);
+ break;
+ }
+ case XLOG_BTREE_VACUUM:
+ {
+ xl_btree_vacuum *xlrec = (xl_btree_vacuum *) rec;
+
+ appendStringInfo(buf, "ndeleted %u; nupdated %u",
+ xlrec->ndeleted, xlrec->nupdated);
+ break;
+ }
+ case XLOG_BTREE_DELETE:
+ {
+ xl_btree_delete *xlrec = (xl_btree_delete *) rec;
+
+ appendStringInfo(buf, "latestRemovedXid %u; ndeleted %u; nupdated %u",
+ xlrec->latestRemovedXid, xlrec->ndeleted, xlrec->nupdated);
+ break;
+ }
+ case XLOG_BTREE_MARK_PAGE_HALFDEAD:
+ {
+ xl_btree_mark_page_halfdead *xlrec = (xl_btree_mark_page_halfdead *) rec;
+
+ appendStringInfo(buf, "topparent %u; leaf %u; left %u; right %u",
+ xlrec->topparent, xlrec->leafblk, xlrec->leftblk, xlrec->rightblk);
+ break;
+ }
+ case XLOG_BTREE_UNLINK_PAGE_META:
+ case XLOG_BTREE_UNLINK_PAGE:
+ {
+ xl_btree_unlink_page *xlrec = (xl_btree_unlink_page *) rec;
+
+ appendStringInfo(buf, "left %u; right %u; level %u; safexid %u:%u; ",
+ xlrec->leftsib, xlrec->rightsib, xlrec->level,
+ EpochFromFullTransactionId(xlrec->safexid),
+ XidFromFullTransactionId(xlrec->safexid));
+ appendStringInfo(buf, "leafleft %u; leafright %u; leaftopparent %u",
+ xlrec->leafleftsib, xlrec->leafrightsib,
+ xlrec->leaftopparent);
+ break;
+ }
+ case XLOG_BTREE_NEWROOT:
+ {
+ xl_btree_newroot *xlrec = (xl_btree_newroot *) rec;
+
+ appendStringInfo(buf, "lev %u", xlrec->level);
+ break;
+ }
+ case XLOG_BTREE_REUSE_PAGE:
+ {
+ xl_btree_reuse_page *xlrec = (xl_btree_reuse_page *) rec;
+
+ appendStringInfo(buf, "rel %u/%u/%u; latestRemovedXid %u:%u",
+ xlrec->node.spcNode, xlrec->node.dbNode,
+ xlrec->node.relNode,
+ EpochFromFullTransactionId(xlrec->latestRemovedFullXid),
+ XidFromFullTransactionId(xlrec->latestRemovedFullXid));
+ break;
+ }
+ case XLOG_BTREE_META_CLEANUP:
+ {
+ xl_btree_metadata *xlrec;
+
+ xlrec = (xl_btree_metadata *) XLogRecGetBlockData(record, 0,
+ NULL);
+ appendStringInfo(buf, "last_cleanup_num_delpages %u",
+ xlrec->last_cleanup_num_delpages);
+ break;
+ }
+ }
+}
+
+const char *
+btree_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_BTREE_INSERT_LEAF:
+ id = "INSERT_LEAF";
+ break;
+ case XLOG_BTREE_INSERT_UPPER:
+ id = "INSERT_UPPER";
+ break;
+ case XLOG_BTREE_INSERT_META:
+ id = "INSERT_META";
+ break;
+ case XLOG_BTREE_SPLIT_L:
+ id = "SPLIT_L";
+ break;
+ case XLOG_BTREE_SPLIT_R:
+ id = "SPLIT_R";
+ break;
+ case XLOG_BTREE_INSERT_POST:
+ id = "INSERT_POST";
+ break;
+ case XLOG_BTREE_DEDUP:
+ id = "DEDUP";
+ break;
+ case XLOG_BTREE_VACUUM:
+ id = "VACUUM";
+ break;
+ case XLOG_BTREE_DELETE:
+ id = "DELETE";
+ break;
+ case XLOG_BTREE_MARK_PAGE_HALFDEAD:
+ id = "MARK_PAGE_HALFDEAD";
+ break;
+ case XLOG_BTREE_UNLINK_PAGE:
+ id = "UNLINK_PAGE";
+ break;
+ case XLOG_BTREE_UNLINK_PAGE_META:
+ id = "UNLINK_PAGE_META";
+ break;
+ case XLOG_BTREE_NEWROOT:
+ id = "NEWROOT";
+ break;
+ case XLOG_BTREE_REUSE_PAGE:
+ id = "REUSE_PAGE";
+ break;
+ case XLOG_BTREE_META_CLEANUP:
+ id = "META_CLEANUP";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/relmapdesc.c b/src/backend/access/rmgrdesc/relmapdesc.c
new file mode 100644
index 0000000..43d63eb
--- /dev/null
+++ b/src/backend/access/rmgrdesc/relmapdesc.c
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * relmapdesc.c
+ * rmgr descriptor routines for utils/cache/relmapper.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/relmapdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "utils/relmapper.h"
+
+void
+relmap_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_RELMAP_UPDATE)
+ {
+ xl_relmap_update *xlrec = (xl_relmap_update *) rec;
+
+ appendStringInfo(buf, "database %u tablespace %u size %d",
+ xlrec->dbid, xlrec->tsid, xlrec->nbytes);
+ }
+}
+
+const char *
+relmap_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_RELMAP_UPDATE:
+ id = "UPDATE";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/replorigindesc.c b/src/backend/access/rmgrdesc/replorigindesc.c
new file mode 100644
index 0000000..e3213b1
--- /dev/null
+++ b/src/backend/access/rmgrdesc/replorigindesc.c
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * replorigindesc.c
+ * rmgr descriptor routines for replication/logical/origin.c
+ *
+ * Portions Copyright (c) 2015-2022, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/replorigindesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "replication/origin.h"
+
+void
+replorigin_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ switch (info)
+ {
+ case XLOG_REPLORIGIN_SET:
+ {
+ xl_replorigin_set *xlrec;
+
+ xlrec = (xl_replorigin_set *) rec;
+
+ appendStringInfo(buf, "set %u; lsn %X/%X; force: %d",
+ xlrec->node_id,
+ LSN_FORMAT_ARGS(xlrec->remote_lsn),
+ xlrec->force);
+ break;
+ }
+ case XLOG_REPLORIGIN_DROP:
+ {
+ xl_replorigin_drop *xlrec;
+
+ xlrec = (xl_replorigin_drop *) rec;
+
+ appendStringInfo(buf, "drop %u", xlrec->node_id);
+ break;
+ }
+ }
+}
+
+const char *
+replorigin_identify(uint8 info)
+{
+ switch (info)
+ {
+ case XLOG_REPLORIGIN_SET:
+ return "SET";
+ case XLOG_REPLORIGIN_DROP:
+ return "DROP";
+ default:
+ return NULL;
+ }
+}
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqdesc.c
new file mode 100644
index 0000000..d9b1e60
--- /dev/null
+++ b/src/backend/access/rmgrdesc/seqdesc.c
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqdesc.c
+ * rmgr descriptor routines for commands/sequence.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/seqdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "commands/sequence.h"
+
+
+void
+seq_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+ xl_seq_rec *xlrec = (xl_seq_rec *) rec;
+
+ if (info == XLOG_SEQ_LOG)
+ appendStringInfo(buf, "rel %u/%u/%u",
+ xlrec->node.spcNode, xlrec->node.dbNode,
+ xlrec->node.relNode);
+}
+
+const char *
+seq_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_SEQ_LOG:
+ id = "LOG";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/smgrdesc.c b/src/backend/access/rmgrdesc/smgrdesc.c
new file mode 100644
index 0000000..7547813
--- /dev/null
+++ b/src/backend/access/rmgrdesc/smgrdesc.c
@@ -0,0 +1,61 @@
+/*-------------------------------------------------------------------------
+ *
+ * smgrdesc.c
+ * rmgr descriptor routines for catalog/storage.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/smgrdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/storage_xlog.h"
+
+
+void
+smgr_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_SMGR_CREATE)
+ {
+ xl_smgr_create *xlrec = (xl_smgr_create *) rec;
+ char *path = relpathperm(xlrec->rnode, xlrec->forkNum);
+
+ appendStringInfoString(buf, path);
+ pfree(path);
+ }
+ else if (info == XLOG_SMGR_TRUNCATE)
+ {
+ xl_smgr_truncate *xlrec = (xl_smgr_truncate *) rec;
+ char *path = relpathperm(xlrec->rnode, MAIN_FORKNUM);
+
+ appendStringInfo(buf, "%s to %u blocks flags %d", path,
+ xlrec->blkno, xlrec->flags);
+ pfree(path);
+ }
+}
+
+const char *
+smgr_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_SMGR_CREATE:
+ id = "CREATE";
+ break;
+ case XLOG_SMGR_TRUNCATE:
+ id = "TRUNCATE";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/spgdesc.c b/src/backend/access/rmgrdesc/spgdesc.c
new file mode 100644
index 0000000..d5d921a
--- /dev/null
+++ b/src/backend/access/rmgrdesc/spgdesc.c
@@ -0,0 +1,164 @@
+/*-------------------------------------------------------------------------
+ *
+ * spgdesc.c
+ * rmgr descriptor routines for access/spgist/spgxlog.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/spgdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/spgxlog.h"
+
+void
+spg_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ switch (info)
+ {
+ case XLOG_SPGIST_ADD_LEAF:
+ {
+ spgxlogAddLeaf *xlrec = (spgxlogAddLeaf *) rec;
+
+ appendStringInfo(buf, "off: %u, headoff: %u, parentoff: %u, nodeI: %u",
+ xlrec->offnumLeaf, xlrec->offnumHeadLeaf,
+ xlrec->offnumParent, xlrec->nodeI);
+ if (xlrec->newPage)
+ appendStringInfoString(buf, " (newpage)");
+ if (xlrec->storesNulls)
+ appendStringInfoString(buf, " (nulls)");
+ }
+ break;
+ case XLOG_SPGIST_MOVE_LEAFS:
+ {
+ spgxlogMoveLeafs *xlrec = (spgxlogMoveLeafs *) rec;
+
+ appendStringInfo(buf, "nmoves: %u, parentoff: %u, nodeI: %u",
+ xlrec->nMoves,
+ xlrec->offnumParent, xlrec->nodeI);
+ if (xlrec->newPage)
+ appendStringInfoString(buf, " (newpage)");
+ if (xlrec->replaceDead)
+ appendStringInfoString(buf, " (replacedead)");
+ if (xlrec->storesNulls)
+ appendStringInfoString(buf, " (nulls)");
+ }
+ break;
+ case XLOG_SPGIST_ADD_NODE:
+ {
+ spgxlogAddNode *xlrec = (spgxlogAddNode *) rec;
+
+ appendStringInfo(buf, "off: %u, newoff: %u, parentBlk: %d, "
+ "parentoff: %u, nodeI: %u",
+ xlrec->offnum,
+ xlrec->offnumNew,
+ xlrec->parentBlk,
+ xlrec->offnumParent,
+ xlrec->nodeI);
+ if (xlrec->newPage)
+ appendStringInfoString(buf, " (newpage)");
+ }
+ break;
+ case XLOG_SPGIST_SPLIT_TUPLE:
+ {
+ spgxlogSplitTuple *xlrec = (spgxlogSplitTuple *) rec;
+
+ appendStringInfo(buf, "prefixoff: %u, postfixoff: %u",
+ xlrec->offnumPrefix,
+ xlrec->offnumPostfix);
+ if (xlrec->newPage)
+ appendStringInfoString(buf, " (newpage)");
+ if (xlrec->postfixBlkSame)
+ appendStringInfoString(buf, " (same)");
+ }
+ break;
+ case XLOG_SPGIST_PICKSPLIT:
+ {
+ spgxlogPickSplit *xlrec = (spgxlogPickSplit *) rec;
+
+ appendStringInfo(buf, "ndelete: %u, ninsert: %u, inneroff: %u, "
+ "parentoff: %u, nodeI: %u",
+ xlrec->nDelete, xlrec->nInsert,
+ xlrec->offnumInner,
+ xlrec->offnumParent, xlrec->nodeI);
+ if (xlrec->innerIsParent)
+ appendStringInfoString(buf, " (innerIsParent)");
+ if (xlrec->storesNulls)
+ appendStringInfoString(buf, " (nulls)");
+ if (xlrec->isRootSplit)
+ appendStringInfoString(buf, " (isRootSplit)");
+ }
+ break;
+ case XLOG_SPGIST_VACUUM_LEAF:
+ {
+ spgxlogVacuumLeaf *xlrec = (spgxlogVacuumLeaf *) rec;
+
+ appendStringInfo(buf, "ndead: %u, nplaceholder: %u, nmove: %u, nchain: %u",
+ xlrec->nDead, xlrec->nPlaceholder,
+ xlrec->nMove, xlrec->nChain);
+ }
+ break;
+ case XLOG_SPGIST_VACUUM_ROOT:
+ {
+ spgxlogVacuumRoot *xlrec = (spgxlogVacuumRoot *) rec;
+
+ appendStringInfo(buf, "ndelete: %u",
+ xlrec->nDelete);
+ }
+ break;
+ case XLOG_SPGIST_VACUUM_REDIRECT:
+ {
+ spgxlogVacuumRedirect *xlrec = (spgxlogVacuumRedirect *) rec;
+
+ appendStringInfo(buf, "ntoplaceholder: %u, firstplaceholder: %u, newestredirectxid: %u",
+ xlrec->nToPlaceholder,
+ xlrec->firstPlaceholder,
+ xlrec->newestRedirectXid);
+ }
+ break;
+ }
+}
+
+const char *
+spg_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_SPGIST_ADD_LEAF:
+ id = "ADD_LEAF";
+ break;
+ case XLOG_SPGIST_MOVE_LEAFS:
+ id = "MOVE_LEAFS";
+ break;
+ case XLOG_SPGIST_ADD_NODE:
+ id = "ADD_NODE";
+ break;
+ case XLOG_SPGIST_SPLIT_TUPLE:
+ id = "SPLIT_TUPLE";
+ break;
+ case XLOG_SPGIST_PICKSPLIT:
+ id = "PICKSPLIT";
+ break;
+ case XLOG_SPGIST_VACUUM_LEAF:
+ id = "VACUUM_LEAF";
+ break;
+ case XLOG_SPGIST_VACUUM_ROOT:
+ id = "VACUUM_ROOT";
+ break;
+ case XLOG_SPGIST_VACUUM_REDIRECT:
+ id = "VACUUM_REDIRECT";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/standbydesc.c b/src/backend/access/rmgrdesc/standbydesc.c
new file mode 100644
index 0000000..2dba39e
--- /dev/null
+++ b/src/backend/access/rmgrdesc/standbydesc.c
@@ -0,0 +1,135 @@
+/*-------------------------------------------------------------------------
+ *
+ * standbydesc.c
+ * rmgr descriptor routines for storage/ipc/standby.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/standbydesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "storage/standbydefs.h"
+
+static void
+standby_desc_running_xacts(StringInfo buf, xl_running_xacts *xlrec)
+{
+ int i;
+
+ appendStringInfo(buf, "nextXid %u latestCompletedXid %u oldestRunningXid %u",
+ xlrec->nextXid,
+ xlrec->latestCompletedXid,
+ xlrec->oldestRunningXid);
+ if (xlrec->xcnt > 0)
+ {
+ appendStringInfo(buf, "; %d xacts:", xlrec->xcnt);
+ for (i = 0; i < xlrec->xcnt; i++)
+ appendStringInfo(buf, " %u", xlrec->xids[i]);
+ }
+
+ if (xlrec->subxid_overflow)
+ appendStringInfoString(buf, "; subxid ovf");
+}
+
+void
+standby_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_STANDBY_LOCK)
+ {
+ xl_standby_locks *xlrec = (xl_standby_locks *) rec;
+ int i;
+
+ for (i = 0; i < xlrec->nlocks; i++)
+ appendStringInfo(buf, "xid %u db %u rel %u ",
+ xlrec->locks[i].xid, xlrec->locks[i].dbOid,
+ xlrec->locks[i].relOid);
+ }
+ else if (info == XLOG_RUNNING_XACTS)
+ {
+ xl_running_xacts *xlrec = (xl_running_xacts *) rec;
+
+ standby_desc_running_xacts(buf, xlrec);
+ }
+ else if (info == XLOG_INVALIDATIONS)
+ {
+ xl_invalidations *xlrec = (xl_invalidations *) rec;
+
+ standby_desc_invalidations(buf, xlrec->nmsgs, xlrec->msgs,
+ xlrec->dbId, xlrec->tsId,
+ xlrec->relcacheInitFileInval);
+ }
+}
+
+const char *
+standby_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_STANDBY_LOCK:
+ id = "LOCK";
+ break;
+ case XLOG_RUNNING_XACTS:
+ id = "RUNNING_XACTS";
+ break;
+ case XLOG_INVALIDATIONS:
+ id = "INVALIDATIONS";
+ break;
+ }
+
+ return id;
+}
+
+/*
+ * This routine is used by both standby_desc and xact_desc, because
+ * transaction commits and XLOG_INVALIDATIONS messages contain invalidations;
+ * it seems pointless to duplicate the code.
+ */
+void
+standby_desc_invalidations(StringInfo buf,
+ int nmsgs, SharedInvalidationMessage *msgs,
+ Oid dbId, Oid tsId,
+ bool relcacheInitFileInval)
+{
+ int i;
+
+ /* Do nothing if there are no invalidation messages */
+ if (nmsgs <= 0)
+ return;
+
+ if (relcacheInitFileInval)
+ appendStringInfo(buf, "; relcache init file inval dbid %u tsid %u",
+ dbId, tsId);
+
+ appendStringInfoString(buf, "; inval msgs:");
+ for (i = 0; i < nmsgs; i++)
+ {
+ SharedInvalidationMessage *msg = &msgs[i];
+
+ if (msg->id >= 0)
+ appendStringInfo(buf, " catcache %d", msg->id);
+ else if (msg->id == SHAREDINVALCATALOG_ID)
+ appendStringInfo(buf, " catalog %u", msg->cat.catId);
+ else if (msg->id == SHAREDINVALRELCACHE_ID)
+ appendStringInfo(buf, " relcache %u", msg->rc.relId);
+ /* not expected, but print something anyway */
+ else if (msg->id == SHAREDINVALSMGR_ID)
+ appendStringInfoString(buf, " smgr");
+ /* not expected, but print something anyway */
+ else if (msg->id == SHAREDINVALRELMAP_ID)
+ appendStringInfo(buf, " relmap db %u", msg->rm.dbId);
+ else if (msg->id == SHAREDINVALSNAPSHOT_ID)
+ appendStringInfo(buf, " snapshot %u", msg->sn.relId);
+ else
+ appendStringInfo(buf, " unrecognized id %d", msg->id);
+ }
+}
diff --git a/src/backend/access/rmgrdesc/tblspcdesc.c b/src/backend/access/rmgrdesc/tblspcdesc.c
new file mode 100644
index 0000000..ed94b6e
--- /dev/null
+++ b/src/backend/access/rmgrdesc/tblspcdesc.c
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------
+ *
+ * tblspcdesc.c
+ * rmgr descriptor routines for commands/tablespace.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/tblspcdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "commands/tablespace.h"
+
+
+void
+tblspc_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_TBLSPC_CREATE)
+ {
+ xl_tblspc_create_rec *xlrec = (xl_tblspc_create_rec *) rec;
+
+ appendStringInfo(buf, "%u \"%s\"", xlrec->ts_id, xlrec->ts_path);
+ }
+ else if (info == XLOG_TBLSPC_DROP)
+ {
+ xl_tblspc_drop_rec *xlrec = (xl_tblspc_drop_rec *) rec;
+
+ appendStringInfo(buf, "%u", xlrec->ts_id);
+ }
+}
+
+const char *
+tblspc_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_TBLSPC_CREATE:
+ id = "CREATE";
+ break;
+ case XLOG_TBLSPC_DROP:
+ id = "DROP";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c
new file mode 100644
index 0000000..90b6ac2
--- /dev/null
+++ b/src/backend/access/rmgrdesc/xactdesc.c
@@ -0,0 +1,514 @@
+/*-------------------------------------------------------------------------
+ *
+ * xactdesc.c
+ * rmgr descriptor routines for access/transam/xact.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/xactdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/transam.h"
+#include "access/xact.h"
+#include "replication/origin.h"
+#include "storage/sinval.h"
+#include "storage/standbydefs.h"
+#include "utils/timestamp.h"
+
+/*
+ * Parse the WAL format of an xact commit and abort records into an easier to
+ * understand format.
+ *
+ * This routines are in xactdesc.c because they're accessed in backend (when
+ * replaying WAL) and frontend (pg_waldump) code. This file is the only xact
+ * specific one shared between both. They're complicated enough that
+ * duplication would be bothersome.
+ */
+
+void
+ParseCommitRecord(uint8 info, xl_xact_commit *xlrec, xl_xact_parsed_commit *parsed)
+{
+ char *data = ((char *) xlrec) + MinSizeOfXactCommit;
+
+ memset(parsed, 0, sizeof(*parsed));
+
+ parsed->xinfo = 0; /* default, if no XLOG_XACT_HAS_INFO is
+ * present */
+
+ parsed->xact_time = xlrec->xact_time;
+
+ if (info & XLOG_XACT_HAS_INFO)
+ {
+ xl_xact_xinfo *xl_xinfo = (xl_xact_xinfo *) data;
+
+ parsed->xinfo = xl_xinfo->xinfo;
+
+ data += sizeof(xl_xact_xinfo);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_DBINFO)
+ {
+ xl_xact_dbinfo *xl_dbinfo = (xl_xact_dbinfo *) data;
+
+ parsed->dbId = xl_dbinfo->dbId;
+ parsed->tsId = xl_dbinfo->tsId;
+
+ data += sizeof(xl_xact_dbinfo);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_SUBXACTS)
+ {
+ xl_xact_subxacts *xl_subxacts = (xl_xact_subxacts *) data;
+
+ parsed->nsubxacts = xl_subxacts->nsubxacts;
+ parsed->subxacts = xl_subxacts->subxacts;
+
+ data += MinSizeOfXactSubxacts;
+ data += parsed->nsubxacts * sizeof(TransactionId);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_RELFILENODES)
+ {
+ xl_xact_relfilenodes *xl_relfilenodes = (xl_xact_relfilenodes *) data;
+
+ parsed->nrels = xl_relfilenodes->nrels;
+ parsed->xnodes = xl_relfilenodes->xnodes;
+
+ data += MinSizeOfXactRelfilenodes;
+ data += xl_relfilenodes->nrels * sizeof(RelFileNode);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_DROPPED_STATS)
+ {
+ xl_xact_stats_items *xl_drops = (xl_xact_stats_items *) data;
+
+ parsed->nstats = xl_drops->nitems;
+ parsed->stats = xl_drops->items;
+
+ data += MinSizeOfXactStatsItems;
+ data += xl_drops->nitems * sizeof(xl_xact_stats_item);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_INVALS)
+ {
+ xl_xact_invals *xl_invals = (xl_xact_invals *) data;
+
+ parsed->nmsgs = xl_invals->nmsgs;
+ parsed->msgs = xl_invals->msgs;
+
+ data += MinSizeOfXactInvals;
+ data += xl_invals->nmsgs * sizeof(SharedInvalidationMessage);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_TWOPHASE)
+ {
+ xl_xact_twophase *xl_twophase = (xl_xact_twophase *) data;
+
+ parsed->twophase_xid = xl_twophase->xid;
+
+ data += sizeof(xl_xact_twophase);
+
+ if (parsed->xinfo & XACT_XINFO_HAS_GID)
+ {
+ strlcpy(parsed->twophase_gid, data, sizeof(parsed->twophase_gid));
+ data += strlen(data) + 1;
+ }
+ }
+
+ /* Note: no alignment is guaranteed after this point */
+
+ if (parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
+ {
+ xl_xact_origin xl_origin;
+
+ /* no alignment is guaranteed, so copy onto stack */
+ memcpy(&xl_origin, data, sizeof(xl_origin));
+
+ parsed->origin_lsn = xl_origin.origin_lsn;
+ parsed->origin_timestamp = xl_origin.origin_timestamp;
+
+ data += sizeof(xl_xact_origin);
+ }
+}
+
+void
+ParseAbortRecord(uint8 info, xl_xact_abort *xlrec, xl_xact_parsed_abort *parsed)
+{
+ char *data = ((char *) xlrec) + MinSizeOfXactAbort;
+
+ memset(parsed, 0, sizeof(*parsed));
+
+ parsed->xinfo = 0; /* default, if no XLOG_XACT_HAS_INFO is
+ * present */
+
+ parsed->xact_time = xlrec->xact_time;
+
+ if (info & XLOG_XACT_HAS_INFO)
+ {
+ xl_xact_xinfo *xl_xinfo = (xl_xact_xinfo *) data;
+
+ parsed->xinfo = xl_xinfo->xinfo;
+
+ data += sizeof(xl_xact_xinfo);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_DBINFO)
+ {
+ xl_xact_dbinfo *xl_dbinfo = (xl_xact_dbinfo *) data;
+
+ parsed->dbId = xl_dbinfo->dbId;
+ parsed->tsId = xl_dbinfo->tsId;
+
+ data += sizeof(xl_xact_dbinfo);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_SUBXACTS)
+ {
+ xl_xact_subxacts *xl_subxacts = (xl_xact_subxacts *) data;
+
+ parsed->nsubxacts = xl_subxacts->nsubxacts;
+ parsed->subxacts = xl_subxacts->subxacts;
+
+ data += MinSizeOfXactSubxacts;
+ data += parsed->nsubxacts * sizeof(TransactionId);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_RELFILENODES)
+ {
+ xl_xact_relfilenodes *xl_relfilenodes = (xl_xact_relfilenodes *) data;
+
+ parsed->nrels = xl_relfilenodes->nrels;
+ parsed->xnodes = xl_relfilenodes->xnodes;
+
+ data += MinSizeOfXactRelfilenodes;
+ data += xl_relfilenodes->nrels * sizeof(RelFileNode);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_DROPPED_STATS)
+ {
+ xl_xact_stats_items *xl_drops = (xl_xact_stats_items *) data;
+
+ parsed->nstats = xl_drops->nitems;
+ parsed->stats = xl_drops->items;
+
+ data += MinSizeOfXactStatsItems;
+ data += xl_drops->nitems * sizeof(xl_xact_stats_item);
+ }
+
+ if (parsed->xinfo & XACT_XINFO_HAS_TWOPHASE)
+ {
+ xl_xact_twophase *xl_twophase = (xl_xact_twophase *) data;
+
+ parsed->twophase_xid = xl_twophase->xid;
+
+ data += sizeof(xl_xact_twophase);
+
+ if (parsed->xinfo & XACT_XINFO_HAS_GID)
+ {
+ strlcpy(parsed->twophase_gid, data, sizeof(parsed->twophase_gid));
+ data += strlen(data) + 1;
+ }
+ }
+
+ /* Note: no alignment is guaranteed after this point */
+
+ if (parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
+ {
+ xl_xact_origin xl_origin;
+
+ /* no alignment is guaranteed, so copy onto stack */
+ memcpy(&xl_origin, data, sizeof(xl_origin));
+
+ parsed->origin_lsn = xl_origin.origin_lsn;
+ parsed->origin_timestamp = xl_origin.origin_timestamp;
+
+ data += sizeof(xl_xact_origin);
+ }
+}
+
+/*
+ * ParsePrepareRecord
+ */
+void
+ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parsed_prepare *parsed)
+{
+ char *bufptr;
+
+ bufptr = ((char *) xlrec) + MAXALIGN(sizeof(xl_xact_prepare));
+
+ memset(parsed, 0, sizeof(*parsed));
+
+ parsed->xact_time = xlrec->prepared_at;
+ parsed->origin_lsn = xlrec->origin_lsn;
+ parsed->origin_timestamp = xlrec->origin_timestamp;
+ parsed->twophase_xid = xlrec->xid;
+ parsed->dbId = xlrec->database;
+ parsed->nsubxacts = xlrec->nsubxacts;
+ parsed->nrels = xlrec->ncommitrels;
+ parsed->nabortrels = xlrec->nabortrels;
+ parsed->nmsgs = xlrec->ninvalmsgs;
+
+ strncpy(parsed->twophase_gid, bufptr, xlrec->gidlen);
+ bufptr += MAXALIGN(xlrec->gidlen);
+
+ parsed->subxacts = (TransactionId *) bufptr;
+ bufptr += MAXALIGN(xlrec->nsubxacts * sizeof(TransactionId));
+
+ parsed->xnodes = (RelFileNode *) bufptr;
+ bufptr += MAXALIGN(xlrec->ncommitrels * sizeof(RelFileNode));
+
+ parsed->abortnodes = (RelFileNode *) bufptr;
+ bufptr += MAXALIGN(xlrec->nabortrels * sizeof(RelFileNode));
+
+ parsed->stats = (xl_xact_stats_item *) bufptr;
+ bufptr += MAXALIGN(xlrec->ncommitstats * sizeof(xl_xact_stats_item));
+
+ parsed->abortstats = (xl_xact_stats_item *) bufptr;
+ bufptr += MAXALIGN(xlrec->nabortstats * sizeof(xl_xact_stats_item));
+
+ parsed->msgs = (SharedInvalidationMessage *) bufptr;
+ bufptr += MAXALIGN(xlrec->ninvalmsgs * sizeof(SharedInvalidationMessage));
+}
+
+static void
+xact_desc_relations(StringInfo buf, char *label, int nrels,
+ RelFileNode *xnodes)
+{
+ int i;
+
+ if (nrels > 0)
+ {
+ appendStringInfo(buf, "; %s:", label);
+ for (i = 0; i < nrels; i++)
+ {
+ char *path = relpathperm(xnodes[i], MAIN_FORKNUM);
+
+ appendStringInfo(buf, " %s", path);
+ pfree(path);
+ }
+ }
+}
+
+static void
+xact_desc_subxacts(StringInfo buf, int nsubxacts, TransactionId *subxacts)
+{
+ int i;
+
+ if (nsubxacts > 0)
+ {
+ appendStringInfoString(buf, "; subxacts:");
+ for (i = 0; i < nsubxacts; i++)
+ appendStringInfo(buf, " %u", subxacts[i]);
+ }
+}
+
+static void
+xact_desc_stats(StringInfo buf, const char *label,
+ int ndropped, xl_xact_stats_item *dropped_stats)
+{
+ int i;
+
+ if (ndropped > 0)
+ {
+ appendStringInfo(buf, "; %sdropped stats:", label);
+ for (i = 0; i < ndropped; i++)
+ {
+ appendStringInfo(buf, " %d/%u/%u",
+ dropped_stats[i].kind,
+ dropped_stats[i].dboid,
+ dropped_stats[i].objoid);
+ }
+ }
+}
+
+static void
+xact_desc_commit(StringInfo buf, uint8 info, xl_xact_commit *xlrec, RepOriginId origin_id)
+{
+ xl_xact_parsed_commit parsed;
+
+ ParseCommitRecord(info, xlrec, &parsed);
+
+ /* If this is a prepared xact, show the xid of the original xact */
+ if (TransactionIdIsValid(parsed.twophase_xid))
+ appendStringInfo(buf, "%u: ", parsed.twophase_xid);
+
+ appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
+
+ xact_desc_relations(buf, "rels", parsed.nrels, parsed.xnodes);
+ xact_desc_subxacts(buf, parsed.nsubxacts, parsed.subxacts);
+ xact_desc_stats(buf, "", parsed.nstats, parsed.stats);
+
+ standby_desc_invalidations(buf, parsed.nmsgs, parsed.msgs, parsed.dbId,
+ parsed.tsId,
+ XactCompletionRelcacheInitFileInval(parsed.xinfo));
+
+ if (XactCompletionApplyFeedback(parsed.xinfo))
+ appendStringInfoString(buf, "; apply_feedback");
+
+ if (XactCompletionForceSyncCommit(parsed.xinfo))
+ appendStringInfoString(buf, "; sync");
+
+ if (parsed.xinfo & XACT_XINFO_HAS_ORIGIN)
+ {
+ appendStringInfo(buf, "; origin: node %u, lsn %X/%X, at %s",
+ origin_id,
+ LSN_FORMAT_ARGS(parsed.origin_lsn),
+ timestamptz_to_str(parsed.origin_timestamp));
+ }
+}
+
+static void
+xact_desc_abort(StringInfo buf, uint8 info, xl_xact_abort *xlrec, RepOriginId origin_id)
+{
+ xl_xact_parsed_abort parsed;
+
+ ParseAbortRecord(info, xlrec, &parsed);
+
+ /* If this is a prepared xact, show the xid of the original xact */
+ if (TransactionIdIsValid(parsed.twophase_xid))
+ appendStringInfo(buf, "%u: ", parsed.twophase_xid);
+
+ appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
+
+ xact_desc_relations(buf, "rels", parsed.nrels, parsed.xnodes);
+ xact_desc_subxacts(buf, parsed.nsubxacts, parsed.subxacts);
+
+ if (parsed.xinfo & XACT_XINFO_HAS_ORIGIN)
+ {
+ appendStringInfo(buf, "; origin: node %u, lsn %X/%X, at %s",
+ origin_id,
+ LSN_FORMAT_ARGS(parsed.origin_lsn),
+ timestamptz_to_str(parsed.origin_timestamp));
+ }
+
+ xact_desc_stats(buf, "", parsed.nstats, parsed.stats);
+}
+
+static void
+xact_desc_prepare(StringInfo buf, uint8 info, xl_xact_prepare *xlrec, RepOriginId origin_id)
+{
+ xl_xact_parsed_prepare parsed;
+
+ ParsePrepareRecord(info, xlrec, &parsed);
+
+ appendStringInfo(buf, "gid %s: ", parsed.twophase_gid);
+ appendStringInfoString(buf, timestamptz_to_str(parsed.xact_time));
+
+ xact_desc_relations(buf, "rels(commit)", parsed.nrels, parsed.xnodes);
+ xact_desc_relations(buf, "rels(abort)", parsed.nabortrels,
+ parsed.abortnodes);
+ xact_desc_stats(buf, "commit ", parsed.nstats, parsed.stats);
+ xact_desc_stats(buf, "abort ", parsed.nabortstats, parsed.abortstats);
+ xact_desc_subxacts(buf, parsed.nsubxacts, parsed.subxacts);
+
+ standby_desc_invalidations(buf, parsed.nmsgs, parsed.msgs, parsed.dbId,
+ parsed.tsId, xlrec->initfileinval);
+
+ /*
+ * Check if the replication origin has been set in this record in the same
+ * way as PrepareRedoAdd().
+ */
+ if (origin_id != InvalidRepOriginId)
+ appendStringInfo(buf, "; origin: node %u, lsn %X/%X, at %s",
+ origin_id,
+ LSN_FORMAT_ARGS(parsed.origin_lsn),
+ timestamptz_to_str(parsed.origin_timestamp));
+}
+
+static void
+xact_desc_assignment(StringInfo buf, xl_xact_assignment *xlrec)
+{
+ int i;
+
+ appendStringInfoString(buf, "subxacts:");
+
+ for (i = 0; i < xlrec->nsubxacts; i++)
+ appendStringInfo(buf, " %u", xlrec->xsub[i]);
+}
+
+void
+xact_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK;
+
+ if (info == XLOG_XACT_COMMIT || info == XLOG_XACT_COMMIT_PREPARED)
+ {
+ xl_xact_commit *xlrec = (xl_xact_commit *) rec;
+
+ xact_desc_commit(buf, XLogRecGetInfo(record), xlrec,
+ XLogRecGetOrigin(record));
+ }
+ else if (info == XLOG_XACT_ABORT || info == XLOG_XACT_ABORT_PREPARED)
+ {
+ xl_xact_abort *xlrec = (xl_xact_abort *) rec;
+
+ xact_desc_abort(buf, XLogRecGetInfo(record), xlrec,
+ XLogRecGetOrigin(record));
+ }
+ else if (info == XLOG_XACT_PREPARE)
+ {
+ xl_xact_prepare *xlrec = (xl_xact_prepare *) rec;
+
+ xact_desc_prepare(buf, XLogRecGetInfo(record), xlrec,
+ XLogRecGetOrigin(record));
+ }
+ else if (info == XLOG_XACT_ASSIGNMENT)
+ {
+ xl_xact_assignment *xlrec = (xl_xact_assignment *) rec;
+
+ /*
+ * Note that we ignore the WAL record's xid, since we're more
+ * interested in the top-level xid that issued the record and which
+ * xids are being reported here.
+ */
+ appendStringInfo(buf, "xtop %u: ", xlrec->xtop);
+ xact_desc_assignment(buf, xlrec);
+ }
+ else if (info == XLOG_XACT_INVALIDATIONS)
+ {
+ xl_xact_invals *xlrec = (xl_xact_invals *) rec;
+
+ standby_desc_invalidations(buf, xlrec->nmsgs, xlrec->msgs, InvalidOid,
+ InvalidOid, false);
+ }
+}
+
+const char *
+xact_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & XLOG_XACT_OPMASK)
+ {
+ case XLOG_XACT_COMMIT:
+ id = "COMMIT";
+ break;
+ case XLOG_XACT_PREPARE:
+ id = "PREPARE";
+ break;
+ case XLOG_XACT_ABORT:
+ id = "ABORT";
+ break;
+ case XLOG_XACT_COMMIT_PREPARED:
+ id = "COMMIT_PREPARED";
+ break;
+ case XLOG_XACT_ABORT_PREPARED:
+ id = "ABORT_PREPARED";
+ break;
+ case XLOG_XACT_ASSIGNMENT:
+ id = "ASSIGNMENT";
+ break;
+ case XLOG_XACT_INVALIDATIONS:
+ id = "INVALIDATION";
+ break;
+ }
+
+ return id;
+}
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
new file mode 100644
index 0000000..fefc563
--- /dev/null
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -0,0 +1,331 @@
+/*-------------------------------------------------------------------------
+ *
+ * xlogdesc.c
+ * rmgr descriptor routines for access/transam/xlog.c
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/xlogdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/transam.h"
+#include "access/xlog.h"
+#include "access/xlog_internal.h"
+#include "catalog/pg_control.h"
+#include "utils/guc.h"
+#include "utils/timestamp.h"
+
+/*
+ * GUC support
+ */
+const struct config_enum_entry wal_level_options[] = {
+ {"minimal", WAL_LEVEL_MINIMAL, false},
+ {"replica", WAL_LEVEL_REPLICA, false},
+ {"archive", WAL_LEVEL_REPLICA, true}, /* deprecated */
+ {"hot_standby", WAL_LEVEL_REPLICA, true}, /* deprecated */
+ {"logical", WAL_LEVEL_LOGICAL, false},
+ {NULL, 0, false}
+};
+
+void
+xlog_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_CHECKPOINT_SHUTDOWN ||
+ info == XLOG_CHECKPOINT_ONLINE)
+ {
+ CheckPoint *checkpoint = (CheckPoint *) rec;
+
+ appendStringInfo(buf, "redo %X/%X; "
+ "tli %u; prev tli %u; fpw %s; xid %u:%u; oid %u; multi %u; offset %u; "
+ "oldest xid %u in DB %u; oldest multi %u in DB %u; "
+ "oldest/newest commit timestamp xid: %u/%u; "
+ "oldest running xid %u; %s",
+ LSN_FORMAT_ARGS(checkpoint->redo),
+ checkpoint->ThisTimeLineID,
+ checkpoint->PrevTimeLineID,
+ checkpoint->fullPageWrites ? "true" : "false",
+ EpochFromFullTransactionId(checkpoint->nextXid),
+ XidFromFullTransactionId(checkpoint->nextXid),
+ checkpoint->nextOid,
+ checkpoint->nextMulti,
+ checkpoint->nextMultiOffset,
+ checkpoint->oldestXid,
+ checkpoint->oldestXidDB,
+ checkpoint->oldestMulti,
+ checkpoint->oldestMultiDB,
+ checkpoint->oldestCommitTsXid,
+ checkpoint->newestCommitTsXid,
+ checkpoint->oldestActiveXid,
+ (info == XLOG_CHECKPOINT_SHUTDOWN) ? "shutdown" : "online");
+ }
+ else if (info == XLOG_NEXTOID)
+ {
+ Oid nextOid;
+
+ memcpy(&nextOid, rec, sizeof(Oid));
+ appendStringInfo(buf, "%u", nextOid);
+ }
+ else if (info == XLOG_RESTORE_POINT)
+ {
+ xl_restore_point *xlrec = (xl_restore_point *) rec;
+
+ appendStringInfoString(buf, xlrec->rp_name);
+ }
+ else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT)
+ {
+ /* no further information to print */
+ }
+ else if (info == XLOG_BACKUP_END)
+ {
+ XLogRecPtr startpoint;
+
+ memcpy(&startpoint, rec, sizeof(XLogRecPtr));
+ appendStringInfo(buf, "%X/%X", LSN_FORMAT_ARGS(startpoint));
+ }
+ else if (info == XLOG_PARAMETER_CHANGE)
+ {
+ xl_parameter_change xlrec;
+ const char *wal_level_str;
+ const struct config_enum_entry *entry;
+
+ memcpy(&xlrec, rec, sizeof(xl_parameter_change));
+
+ /* Find a string representation for wal_level */
+ wal_level_str = "?";
+ for (entry = wal_level_options; entry->name; entry++)
+ {
+ if (entry->val == xlrec.wal_level)
+ {
+ wal_level_str = entry->name;
+ break;
+ }
+ }
+
+ appendStringInfo(buf, "max_connections=%d max_worker_processes=%d "
+ "max_wal_senders=%d max_prepared_xacts=%d "
+ "max_locks_per_xact=%d wal_level=%s "
+ "wal_log_hints=%s track_commit_timestamp=%s",
+ xlrec.MaxConnections,
+ xlrec.max_worker_processes,
+ xlrec.max_wal_senders,
+ xlrec.max_prepared_xacts,
+ xlrec.max_locks_per_xact,
+ wal_level_str,
+ xlrec.wal_log_hints ? "on" : "off",
+ xlrec.track_commit_timestamp ? "on" : "off");
+ }
+ else if (info == XLOG_FPW_CHANGE)
+ {
+ bool fpw;
+
+ memcpy(&fpw, rec, sizeof(bool));
+ appendStringInfoString(buf, fpw ? "true" : "false");
+ }
+ else if (info == XLOG_END_OF_RECOVERY)
+ {
+ xl_end_of_recovery xlrec;
+
+ memcpy(&xlrec, rec, sizeof(xl_end_of_recovery));
+ appendStringInfo(buf, "tli %u; prev tli %u; time %s",
+ xlrec.ThisTimeLineID, xlrec.PrevTimeLineID,
+ timestamptz_to_str(xlrec.end_time));
+ }
+ else if (info == XLOG_OVERWRITE_CONTRECORD)
+ {
+ xl_overwrite_contrecord xlrec;
+
+ memcpy(&xlrec, rec, sizeof(xl_overwrite_contrecord));
+ appendStringInfo(buf, "lsn %X/%X; time %s",
+ LSN_FORMAT_ARGS(xlrec.overwritten_lsn),
+ timestamptz_to_str(xlrec.overwrite_time));
+ }
+}
+
+const char *
+xlog_identify(uint8 info)
+{
+ const char *id = NULL;
+
+ switch (info & ~XLR_INFO_MASK)
+ {
+ case XLOG_CHECKPOINT_SHUTDOWN:
+ id = "CHECKPOINT_SHUTDOWN";
+ break;
+ case XLOG_CHECKPOINT_ONLINE:
+ id = "CHECKPOINT_ONLINE";
+ break;
+ case XLOG_NOOP:
+ id = "NOOP";
+ break;
+ case XLOG_NEXTOID:
+ id = "NEXTOID";
+ break;
+ case XLOG_SWITCH:
+ id = "SWITCH";
+ break;
+ case XLOG_BACKUP_END:
+ id = "BACKUP_END";
+ break;
+ case XLOG_PARAMETER_CHANGE:
+ id = "PARAMETER_CHANGE";
+ break;
+ case XLOG_RESTORE_POINT:
+ id = "RESTORE_POINT";
+ break;
+ case XLOG_FPW_CHANGE:
+ id = "FPW_CHANGE";
+ break;
+ case XLOG_END_OF_RECOVERY:
+ id = "END_OF_RECOVERY";
+ break;
+ case XLOG_OVERWRITE_CONTRECORD:
+ id = "OVERWRITE_CONTRECORD";
+ break;
+ case XLOG_FPI:
+ id = "FPI";
+ break;
+ case XLOG_FPI_FOR_HINT:
+ id = "FPI_FOR_HINT";
+ break;
+ }
+
+ return id;
+}
+
+/*
+ * Returns a string giving information about all the blocks in an
+ * XLogRecord.
+ */
+void
+XLogRecGetBlockRefInfo(XLogReaderState *record, bool pretty,
+ bool detailed_format, StringInfo buf,
+ uint32 *fpi_len)
+{
+ int block_id;
+
+ Assert(record != NULL);
+
+ if (detailed_format && pretty)
+ appendStringInfoChar(buf, '\n');
+
+ for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
+ {
+ RelFileNode rnode;
+ ForkNumber forknum;
+ BlockNumber blk;
+
+ if (!XLogRecGetBlockTagExtended(record, block_id,
+ &rnode, &forknum, &blk, NULL))
+ continue;
+
+ if (detailed_format)
+ {
+ /* Get block references in detailed format. */
+
+ if (pretty)
+ appendStringInfoChar(buf, '\t');
+ else if (block_id > 0)
+ appendStringInfoChar(buf, ' ');
+
+ appendStringInfo(buf,
+ "blkref #%d: rel %u/%u/%u fork %s blk %u",
+ block_id,
+ rnode.spcNode, rnode.dbNode, rnode.relNode,
+ forkNames[forknum],
+ blk);
+
+ if (XLogRecHasBlockImage(record, block_id))
+ {
+ uint8 bimg_info = XLogRecGetBlock(record, block_id)->bimg_info;
+
+ /* Calculate the amount of FPI data in the record. */
+ if (fpi_len)
+ *fpi_len += XLogRecGetBlock(record, block_id)->bimg_len;
+
+ if (BKPIMAGE_COMPRESSED(bimg_info))
+ {
+ const char *method;
+
+ if ((bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
+ method = "pglz";
+ else if ((bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
+ method = "lz4";
+ else if ((bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0)
+ method = "zstd";
+ else
+ method = "unknown";
+
+ appendStringInfo(buf,
+ " (FPW%s); hole: offset: %u, length: %u, "
+ "compression saved: %u, method: %s",
+ XLogRecBlockImageApply(record, block_id) ?
+ "" : " for WAL verification",
+ XLogRecGetBlock(record, block_id)->hole_offset,
+ XLogRecGetBlock(record, block_id)->hole_length,
+ BLCKSZ -
+ XLogRecGetBlock(record, block_id)->hole_length -
+ XLogRecGetBlock(record, block_id)->bimg_len,
+ method);
+ }
+ else
+ {
+ appendStringInfo(buf,
+ " (FPW%s); hole: offset: %u, length: %u",
+ XLogRecBlockImageApply(record, block_id) ?
+ "" : " for WAL verification",
+ XLogRecGetBlock(record, block_id)->hole_offset,
+ XLogRecGetBlock(record, block_id)->hole_length);
+ }
+ }
+
+ if (pretty)
+ appendStringInfoChar(buf, '\n');
+ }
+ else
+ {
+ /* Get block references in short format. */
+
+ if (forknum != MAIN_FORKNUM)
+ {
+ appendStringInfo(buf,
+ ", blkref #%d: rel %u/%u/%u fork %s blk %u",
+ block_id,
+ rnode.spcNode, rnode.dbNode, rnode.relNode,
+ forkNames[forknum],
+ blk);
+ }
+ else
+ {
+ appendStringInfo(buf,
+ ", blkref #%d: rel %u/%u/%u blk %u",
+ block_id,
+ rnode.spcNode, rnode.dbNode, rnode.relNode,
+ blk);
+ }
+
+ if (XLogRecHasBlockImage(record, block_id))
+ {
+ /* Calculate the amount of FPI data in the record. */
+ if (fpi_len)
+ *fpi_len += XLogRecGetBlock(record, block_id)->bimg_len;
+
+ if (XLogRecBlockImageApply(record, block_id))
+ appendStringInfo(buf, " FPW");
+ else
+ appendStringInfo(buf, " FPW for WAL verification");
+ }
+ }
+ }
+
+ if (!detailed_format && pretty)
+ appendStringInfoChar(buf, '\n');
+}