0002-generic-xlog.11.patch
application/octet-stream
Filename: 0002-generic-xlog.11.patch
Type: application/octet-stream
Part: 1
Message:
Re: WIP: Access method extendability
Patch
Format: unified
Series: patch 0002
| File | + | − |
|---|---|---|
| src/backend/access/rmgrdesc/genericdesc.c | 0 | 0 |
| src/backend/access/rmgrdesc/Makefile | 0 | 0 |
| src/backend/access/transam/generic_xlog.c | 0 | 0 |
| src/backend/access/transam/Makefile | 0 | 0 |
| src/backend/access/transam/rmgr.c | 0 | 0 |
| src/backend/replication/logical/decode.c | 0 | 0 |
| src/bin/pg_xlogdump/.gitignore | 0 | 0 |
| src/bin/pg_xlogdump/rmgrdesc.c | 0 | 0 |
| src/include/access/generic_xlog.h | 0 | 0 |
| src/include/access/rmgrlist.h | 0 | 0 |
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
new file mode 100644
index c72a1f2..c0e38fd
*** a/src/backend/access/rmgrdesc/Makefile
--- b/src/backend/access/rmgrdesc/Makefile
*************** subdir = src/backend/access/rmgrdesc
*** 8,16 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = brindesc.o clogdesc.o committsdesc.o dbasedesc.o gindesc.o gistdesc.o \
! hashdesc.o heapdesc.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
--- 8,16 ----
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 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/genericdesc.c b/src/backend/access/rmgrdesc/genericdesc.c
new file mode 100644
index ...3d035c2
*** a/src/backend/access/rmgrdesc/genericdesc.c
--- b/src/backend/access/rmgrdesc/genericdesc.c
***************
*** 0 ****
--- 1,58 ----
+ /*-------------------------------------------------------------------------
+ *
+ * genericdesc.c
+ * rmgr descriptor routines for access/transam/generic_xlog.c
+ *
+ *
+ * Portions Copyright (c) 1996-2014, 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 which 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);
+ }
+
+ return;
+ }
+
+ /*
+ * 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/transam/Makefile b/src/backend/access/transam/Makefile
new file mode 100644
index 94455b2..16fbe47
*** a/src/backend/access/transam/Makefile
--- b/src/backend/access/transam/Makefile
*************** subdir = src/backend/access/transam
*** 12,19 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = clog.o commit_ts.o multixact.o parallel.o rmgr.o slru.o subtrans.o \
! timeline.o transam.o twophase.o twophase_rmgr.o varsup.o \
xact.o xlog.o xlogarchive.o xlogfuncs.o \
xloginsert.o xlogreader.o xlogutils.o
--- 12,19 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = clog.o commit_ts.o generic_xlog.o multixact.o parallel.o rmgr.o slru.o \
! subtrans.o timeline.o transam.o twophase.o twophase_rmgr.o varsup.o \
xact.o xlog.o xlogarchive.o xlogfuncs.o \
xloginsert.o xlogreader.o xlogutils.o
diff --git a/src/backend/access/transam/generic_xlog.c b/src/backend/access/transam/generic_xlog.c
new file mode 100644
index ...e6f26a5
*** a/src/backend/access/transam/generic_xlog.c
--- b/src/backend/access/transam/generic_xlog.c
***************
*** 0 ****
--- 1,508 ----
+ /*-------------------------------------------------------------------------
+ *
+ * generic_xlog.c
+ * Implementation of generic xlog records.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/access/transam/generic_xlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include "access/generic_xlog.h"
+ #include "access/xlogutils.h"
+ #include "miscadmin.h"
+ #include "utils/memutils.h"
+
+ /*-------------------------------------------------------------------------
+ * API for construction of generic xlog records
+ *
+ * This API allows user to construct generic xlog records which are
+ * describing difference between pages in general way. Thus it's useful
+ * for extension which provides custom access methods because they couldn't
+ * register their own WAL redo routines.
+ *
+ * Generic xlog record should be constructed in following steps.
+ * 1) GenericXLogStart(relation) - start construction of generic xlog
+ * record for given relation.
+ * 2) GenericXLogRegister(buffer, isNew) - register one or more buffers
+ * for generic xlog record. This function return a copy of page image
+ * where modifications should be performed. The second argument
+ * indicates that block is new and full image should be taken.
+ * 3) Do modification of page images obtained in previous step.
+ * 4) GenericXLogFinish() - finish construction of generic xlog record.
+ *
+ * Please, note following points while constructing generic xlog records.
+ * - No direct modifications of page images are allowed! All modifications
+ * should be done in copies returned by GenericXLogRegister(). Literally
+ * code which makes generic xlog records should never call
+ * BufferGetPage() function.
+ * - On any step generic xlog record construction could be canceled by
+ * calling GenericXLogAbort(). All changes made in page images copies
+ * would be discarded.
+ * - Registrations of buffers (step 2) and modifications of page images
+ * (step 3) could be mixed in any sequence. The only restriction is that
+ * you can modify page image only after registration of corresponding
+ * buffer.
+ * - After registration buffer also can be unregistered by calling
+ * GenericXLogUnregister(buffer). In this case changes made in particular
+ * page image copy will be discarded.
+ * - Generic xlog assumes that pages are using standard layout. I.e. all
+ * information between pd_lower and pd_upper will be discarded.
+ * - Maximum number of buffers simultaneously registered for generic xlog
+ * is MAX_GENERIC_XLOG_PAGES. Error would be thrown if this limit
+ * exceeded.
+ * - Since you modify copies of page images, GenericXLogStart() doesn't
+ * start a critical section. Thus, you can do memory allocation, error
+ * throwing etc between GenericXLogStart() and GenericXLogFinish().
+ * Actual critical section present inside GenericXLogFinish().
+ * - GenericXLogFinish() takes care about marking buffers dirty and setting
+ * their LSNs. You don't need to do this explicitly.
+ * - For unlogged relations, everything work the same expect there is no
+ * WAL record produced. Thus, you typically don't need to do any explicit
+ * checks for unlogged relations.
+ * - If registered buffer isn't new, generic xlog record contains delta
+ * between old and new page images. This delta is produced by per byte
+ * comparison. Current delta mechanist is not effective for data shift
+ * inside the page. However, it could be improved in further versions.
+ * - Generic xlog redo function will acquire exclusive locks to buffers
+ * in the same order they were registered. After redo of all changes
+ * locks would be released in the same order. That could makes sense for
+ * concurrency.
+ *
+ * Internally delta between pages consists of set of fragments. Each fragment
+ * represents changes made in given region of page. Fragment is described
+ * as following.
+ *
+ * - offset of page region (OffsetNumber)
+ * - length of page region (OffsetNumber)
+ * - data - the data to place into described region ('length' number of bytes)
+ *
+ * Unchanged regions of page are uncovered by these fragments. This is why
+ * delta could be more compact than full page image. But if unchanged region
+ * of page is less than fragment header (offset and length) then it would
+ * increase size of delta instead of decreasing. Thus, we break fragment only
+ * for unchanged regions greater than MATCH_THRESHOLD.
+ *
+ * The worst case for delta size is when we didn't find any unchanged region
+ * in the page. Then size of delta would be size of page plus size of fragment
+ * header.
+ */
+ #define FRAGMENT_HEADER_SIZE (2 * sizeof(OffsetNumber))
+ #define MATCH_THRESHOLD FRAGMENT_HEADER_SIZE
+ #define MAX_DELTA_SIZE BLCKSZ + FRAGMENT_HEADER_SIZE
+
+ /* Struct of generic xlog data for single page */
+ typedef struct
+ {
+ Buffer buffer; /* registered buffer */
+ char image[BLCKSZ]; /* copy of page image for modification */
+ char data[MAX_DELTA_SIZE]; /* delta between page images */
+ int dataLen; /* space consumed in data field */
+ bool fullImage; /* are we taking full image of this page? */
+ } PageData;
+
+ /* Enum of generic xlog (gxlog) status */
+ enum GenericXlogStatus
+ {
+ GXLOG_NOT_STARTED, /* gxlog is not started */
+ GXLOG_LOGGED, /* gxlog is started for logged relation */
+ GXLOG_UNLOGGED /* gxlog is started for unlogged relation */
+ };
+
+ static enum GenericXlogStatus genericXlogStatus = GXLOG_NOT_STARTED;
+ static PageData pages[MAX_GENERIC_XLOG_PAGES];
+
+
+ static void writeFragment(PageData *pageData, OffsetNumber offset,
+ OffsetNumber len, Pointer data);
+ static void writeDelta(PageData *pageData);
+ static void applyPageRedo(Page page, Pointer data, Size dataSize);
+
+ /*
+ * Write next fragment into delta.
+ */
+ static void
+ writeFragment(PageData *pageData, OffsetNumber offset, OffsetNumber length,
+ Pointer data)
+ {
+ Pointer ptr = pageData->data + pageData->dataLen;
+
+ /* Check we have enough of space */
+ Assert(pageData->dataLen + sizeof(offset) +
+ sizeof(length) + length <= sizeof(pageData->data));
+
+ /* Write fragment data */
+ memcpy(ptr, &offset, sizeof(offset));
+ ptr += sizeof(offset);
+ memcpy(ptr, &length, sizeof(length));
+ ptr += sizeof(length);
+ memcpy(ptr, data, length);
+ ptr += length;
+
+ pageData->dataLen = ptr - pageData->data;
+ }
+
+ /*
+ * Make delta for given page.
+ */
+ static void
+ writeDelta(PageData *pageData)
+ {
+ Page page = BufferGetPage(pageData->buffer),
+ image = (Page) pageData->image;
+ int i,
+ fragmentBegin = -1,
+ fragmentEnd = -1;
+ uint16 pageLower = ((PageHeader) page)->pd_lower,
+ pageUpper = ((PageHeader) page)->pd_upper,
+ imageLower = ((PageHeader) image)->pd_lower,
+ imageUpper = ((PageHeader) image)->pd_upper;
+
+ for (i = 0; i < BLCKSZ; i++)
+ {
+ bool match;
+
+ /*
+ * Check if bytes in old and new page images matches. We don't rely
+ * data in unallocated area between pd_lower and pd_upper. Thus we
+ * assume unallocated area to expand with unmatched bytes. Bytes
+ * inside unallocated area are assumed to always match.
+ */
+ if (i < pageLower)
+ {
+ if (i < imageLower)
+ match = (page[i] == image[i]);
+ else
+ match = false;
+ }
+ else if (i >= pageUpper)
+ {
+ if (i >= imageUpper)
+ match = (page[i] == image[i]);
+ else
+ match = false;
+ }
+ else
+ {
+ match = true;
+ }
+
+ if (match)
+ {
+ if (fragmentBegin >= 0)
+ {
+ /* Matched byte is potential of fragment. */
+ if (fragmentEnd < 0)
+ fragmentEnd = i;
+
+ /*
+ * Write next fragment if sequence of matched bytes is longer
+ * than MATCH_THRESHOLD.
+ */
+ if (i - fragmentEnd >= MATCH_THRESHOLD)
+ {
+ writeFragment(pageData, fragmentBegin,
+ fragmentEnd - fragmentBegin,
+ page + fragmentBegin);
+ fragmentBegin = -1;
+ fragmentEnd = -1;
+ }
+ }
+ }
+ else
+ {
+ /* On unmatched byte, start new fragment if it's not done yet */
+ if (fragmentBegin < 0)
+ fragmentBegin = i;
+ fragmentEnd = -1;
+ }
+ }
+
+ if (fragmentBegin >= 0)
+ writeFragment(pageData, fragmentBegin,
+ BLCKSZ - fragmentBegin,
+ page + fragmentBegin);
+
+ #ifdef WAL_DEBUG
+ /*
+ * If xlog debug is enabled then check produced delta. Result of delta
+ * application to saved image should be the same as current page state.
+ */
+ if (XLOG_DEBUG)
+ {
+ char tmp[BLCKSZ];
+ memcpy(tmp, image, BLCKSZ);
+ applyPageRedo(tmp, pageData->data, pageData->dataLen);
+ elog(ERROR, "result of generic xlog apply doesn't match");
+ }
+ #endif
+ }
+
+ /*
+ * Start new generic xlog record.
+ */
+ void
+ GenericXLogStart(Relation relation)
+ {
+ int i;
+
+ if (genericXlogStatus != GXLOG_NOT_STARTED)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("GenericXLogStart: generic xlog is already started")));
+
+ genericXlogStatus = RelationNeedsWAL(relation) ? GXLOG_LOGGED : GXLOG_UNLOGGED;
+
+ for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++)
+ {
+ pages[i].buffer = InvalidBuffer;
+ }
+ }
+
+ /*
+ * Register new buffer for generic xlog record.
+ */
+ Page
+ GenericXLogRegister(Buffer buffer, bool isNew)
+ {
+ int block_id;
+
+ if (genericXlogStatus == GXLOG_NOT_STARTED)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("GenericXLogRegister: generic xlog isn't started")));
+
+ /* Place new buffer to unused slot in array */
+ for (block_id = 0; block_id < MAX_GENERIC_XLOG_PAGES; block_id++)
+ {
+ if (BufferIsInvalid(pages[block_id].buffer))
+ {
+ pages[block_id].buffer = buffer;
+ memcpy(pages[block_id].image, BufferGetPage(buffer), BLCKSZ);
+ pages[block_id].dataLen = 0;
+ pages[block_id].fullImage = isNew;
+ return (Page)pages[block_id].image;
+ }
+ else if (pages[block_id].buffer == buffer)
+ {
+ /*
+ * Buffer already registered. Just return image which is already
+ * prepared.
+ */
+ return (Page)pages[block_id].image;
+ }
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("GenericXLogRegister: maximum number of %d buffers is exceeded",
+ MAX_GENERIC_XLOG_PAGES)));
+
+ /* keep compiler quiet */
+ return NULL;
+ }
+
+ /*
+ * Unregister particular buffer for generic xlog record.
+ */
+ void
+ GenericXLogUnregister(Buffer buffer)
+ {
+ int block_id;
+
+ if (genericXlogStatus == GXLOG_NOT_STARTED)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("GenericXLogUnregister: generic xlog isn't started")));
+
+ /* Find block in array to unregister */
+ for (block_id = 0; block_id < MAX_GENERIC_XLOG_PAGES; block_id++)
+ {
+ if (pages[block_id].buffer == buffer)
+ {
+ /*
+ * Preserve order of pages in array because it could matter for
+ * concurrency.
+ */
+ memmove(&pages[block_id], &pages[block_id + 1],
+ (MAX_GENERIC_XLOG_PAGES - block_id - 1) * sizeof(PageData));
+ pages[MAX_GENERIC_XLOG_PAGES - 1].buffer = InvalidBuffer;
+ return;
+ }
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("GenericXLogUnregister: registered buffer not found")));
+ }
+
+ /*
+ * Put all changes in registered buffers to generic xlog record.
+ */
+ XLogRecPtr
+ GenericXLogFinish(void)
+ {
+ XLogRecPtr lsn = InvalidXLogRecPtr;
+ int i;
+
+ if (genericXlogStatus == GXLOG_LOGGED)
+ {
+ /* Logged relation: make xlog record in critical section. */
+ START_CRIT_SECTION();
+ XLogBeginInsert();
+
+ for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++)
+ {
+ char tmp[BLCKSZ];
+
+ if (BufferIsInvalid(pages[i].buffer))
+ continue;
+
+ /* Swap current and saved page image. */
+ memcpy(tmp, pages[i].image, BLCKSZ);
+ memcpy(pages[i].image, BufferGetPage(pages[i].buffer), BLCKSZ);
+ memcpy(BufferGetPage(pages[i].buffer), tmp, BLCKSZ);
+
+ if (pages[i].fullImage)
+ {
+ /* Full page image doesn't require anything special from us */
+ XLogRegisterBuffer(i, pages[i].buffer, REGBUF_FORCE_IMAGE);
+ }
+ else
+ {
+ /*
+ * In normal node calculate delta and write use it as data
+ * associated with this page.
+ */
+ XLogRegisterBuffer(i, pages[i].buffer, REGBUF_STANDARD);
+ writeDelta(&pages[i]);
+ XLogRegisterBufData(i, pages[i].data, pages[i].dataLen);
+ }
+ }
+
+ /* Insert xlog record */
+ lsn = XLogInsert(RM_GENERIC_ID, 0);
+
+ /* Set LSN and make buffers dirty */
+ for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++)
+ {
+ if (BufferIsInvalid(pages[i].buffer))
+ continue;
+ PageSetLSN(BufferGetPage(pages[i].buffer), lsn);
+ MarkBufferDirty(pages[i].buffer);
+ }
+ END_CRIT_SECTION();
+ }
+ else if (genericXlogStatus == GXLOG_UNLOGGED)
+ {
+ /* Unlogged relation: skip xlog-related stuff */
+ START_CRIT_SECTION();
+ for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++)
+ {
+ if (BufferIsInvalid(pages[i].buffer))
+ continue;
+ memcpy(BufferGetPage(pages[i].buffer), pages[i].image, BLCKSZ);
+ MarkBufferDirty(pages[i].buffer);
+ }
+ END_CRIT_SECTION();
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("GenericXLogFinish: generic xlog isn't started")));
+ }
+
+ genericXlogStatus = GXLOG_NOT_STARTED;
+
+ return lsn;
+ }
+
+ /*
+ * Abort generic xlog record.
+ */
+ void
+ GenericXLogAbort(void)
+ {
+ if (genericXlogStatus == GXLOG_NOT_STARTED)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("GenericXLogAbort: generic xlog isn't started")));
+
+ genericXlogStatus = GXLOG_NOT_STARTED;
+ }
+
+ /*
+ * Apply delta to given page image.
+ */
+ static void
+ applyPageRedo(Page page, Pointer data, Size dataSize)
+ {
+ Pointer ptr = data, end = data + dataSize;
+
+ while (ptr < end)
+ {
+ OffsetNumber offset,
+ length;
+
+ memcpy(&offset, ptr, sizeof(offset));
+ ptr += sizeof(offset);
+ memcpy(&length, ptr, sizeof(length));
+ ptr += sizeof(length);
+
+ memcpy(page + offset, ptr, length);
+
+ ptr += length;
+ }
+ }
+
+ /*
+ * Redo function for generic xlog record.
+ */
+ void
+ generic_redo(XLogReaderState *record)
+ {
+ uint8 block_id;
+ Buffer buffers[MAX_GENERIC_XLOG_PAGES] = {InvalidBuffer};
+ XLogRecPtr lsn = record->EndRecPtr;
+
+ Assert(record->max_block_id < MAX_GENERIC_XLOG_PAGES);
+
+ /* Interate over blocks */
+ for (block_id = 0; block_id <= record->max_block_id; block_id++)
+ {
+ XLogRedoAction action;
+
+ if (!XLogRecHasBlockRef(record, block_id))
+ continue;
+
+ action = XLogReadBufferForRedo(record, block_id, &buffers[block_id]);
+
+ /* Apply redo to given block if needed */
+ if (action == BLK_NEEDS_REDO)
+ {
+ Pointer blockData;
+ Size blockDataSize;
+ Page page;
+
+ page = BufferGetPage(buffers[block_id]);
+ blockData = XLogRecGetBlockData(record, block_id, &blockDataSize);
+ applyPageRedo(page, blockData, blockDataSize);
+
+ PageSetLSN(page, lsn);
+ MarkBufferDirty(buffers[block_id]);
+ }
+ }
+
+ /* Changes are done: unlock and release all buffers */
+ for (block_id = 0; block_id <= record->max_block_id; block_id++)
+ {
+ if (BufferIsValid(buffers[block_id]))
+ UnlockReleaseBuffer(buffers[block_id]);
+ }
+ }
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
new file mode 100644
index 7c4d773..7b38c16
*** a/src/backend/access/transam/rmgr.c
--- b/src/backend/access/transam/rmgr.c
***************
*** 11,16 ****
--- 11,17 ----
#include "access/commit_ts.h"
#include "access/gin.h"
#include "access/gist_private.h"
+ #include "access/generic_xlog.h"
#include "access/hash.h"
#include "access/heapam_xlog.h"
#include "access/brin_xlog.h"
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
new file mode 100644
index 13af485..262deb2
*** a/src/backend/replication/logical/decode.c
--- b/src/backend/replication/logical/decode.c
*************** LogicalDecodingProcessRecord(LogicalDeco
*** 143,148 ****
--- 143,149 ----
case RM_BRIN_ID:
case RM_COMMIT_TS_ID:
case RM_REPLORIGIN_ID:
+ case RM_GENERIC_ID:
/* just deal with xid, and done */
ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(record),
buf.origptr);
diff --git a/src/bin/pg_xlogdump/.gitignore b/src/bin/pg_xlogdump/.gitignore
new file mode 100644
index eebaf30..33a1acf
*** a/src/bin/pg_xlogdump/.gitignore
--- b/src/bin/pg_xlogdump/.gitignore
***************
*** 4,9 ****
--- 4,10 ----
/clogdesc.c
/committsdesc.c
/dbasedesc.c
+ /genericdesc.c
/gindesc.c
/gistdesc.c
/hashdesc.c
diff --git a/src/bin/pg_xlogdump/rmgrdesc.c b/src/bin/pg_xlogdump/rmgrdesc.c
new file mode 100644
index f9cd395..cff7e59
*** a/src/bin/pg_xlogdump/rmgrdesc.c
--- b/src/bin/pg_xlogdump/rmgrdesc.c
***************
*** 11,16 ****
--- 11,17 ----
#include "access/brin_xlog.h"
#include "access/clog.h"
#include "access/commit_ts.h"
+ #include "access/generic_xlog.h"
#include "access/gin.h"
#include "access/gist_private.h"
#include "access/hash.h"
diff --git a/src/include/access/generic_xlog.h b/src/include/access/generic_xlog.h
new file mode 100644
index ...49249e0
*** a/src/include/access/generic_xlog.h
--- b/src/include/access/generic_xlog.h
***************
*** 0 ****
--- 1,36 ----
+ /*-------------------------------------------------------------------------
+ *
+ * generic_xlog.h
+ * Generic xlog API definition.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/generic_xlog.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef GENERIC_XLOG_H
+ #define GENERIC_XLOG_H
+
+ #include "access/xlog.h"
+ #include "access/xlog_internal.h"
+ #include "storage/bufpage.h"
+ #include "utils/rel.h"
+
+ #define MAX_GENERIC_XLOG_PAGES 3
+
+ /* API for construction of generic xlog records */
+ extern void GenericXLogStart(Relation relation);
+ extern Page GenericXLogRegister(Buffer buffer, bool isNew);
+ extern void GenericXLogUnregister(Buffer buffer);
+ extern XLogRecPtr GenericXLogFinish(void);
+ extern void GenericXLogAbort(void);
+
+ /* functions defined for rmgr */
+ extern void generic_redo(XLogReaderState *record);
+ extern const char *generic_identify(uint8 info);
+ extern void generic_desc(StringInfo buf, XLogReaderState *record);
+
+ #endif /* GENERIC_XLOG_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
new file mode 100644
index fab912d..3cfe6f7
*** a/src/include/access/rmgrlist.h
--- b/src/include/access/rmgrlist.h
*************** PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo
*** 45,47 ****
--- 45,48 ----
PG_RMGR(RM_BRIN_ID, "BRIN", brin_redo, brin_desc, brin_identify, NULL, NULL)
PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL)
PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL)
+ PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL)