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 ...46c430e *** a/src/backend/access/rmgrdesc/genericdesc.c --- b/src/backend/access/rmgrdesc/genericdesc.c *************** *** 0 **** --- 1,31 ---- + /*------------------------------------------------------------------------- + * + * 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 + * + * + * IDENTIFICATION + * src/backend/access/rmgrdesc/genericdesc.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "access/generic_xlog.h" + #include "lib/stringinfo.h" + #include "storage/relfilenode.h" + + void + generic_desc(StringInfo buf, XLogReaderState *record) + { + return; + } + + 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 ...3b5b0dd *** a/src/backend/access/transam/generic_xlog.c --- b/src/backend/access/transam/generic_xlog.c *************** *** 0 **** --- 1,624 ---- + /*------------------------------------------------------------------------- + * + * generic_xlog.c + * WAL replay logic for generic XLOG. + * + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * 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" + + #define MAX_REGIONS 256 + #define MATCH_THRESHOLD 16 + #define MAX_GENERIC_XLOG_PAGES 3 + + #define MOVE_FLAG 0x8000 + #define LENGTH_MASK 0x7FFF + + /* #define DEBUG_PRINT */ + + typedef struct + { + OffsetNumber dstOffset, srcOffset, length; + } Region; + + typedef struct + { + Buffer buffer; + char image[BLCKSZ]; + Region regions[MAX_REGIONS]; + int regionsCount; + bool overflow; + char data[2 * BLCKSZ]; + int dataLen; + bool fullImage; + } PageData; + + enum GenericXlogStatus + { + GXLOG_NOT_STARTED, + GXLOG_LOGGED, + GXLOG_UNLOGGED + }; + + + static enum GenericXlogStatus genericXlogStatus = GXLOG_NOT_STARTED; + static PageData pages[MAX_GENERIC_XLOG_PAGES]; + + static int + regionOffsetCmp(const void *a, const void *b) + { + OffsetNumber off = *((const OffsetNumber *)a); + const Region *r = (const Region *)b; + + if (off < r->dstOffset) + return -1; + else if (off >= r->dstOffset + r->length) + return 1; + else + return 0; + } + + static void + initPageData(PageData *pageData, Buffer buffer) + { + pageData->buffer = buffer; + memcpy(pageData->image, BufferGetPage(buffer), BLCKSZ); + pageData->regions[0].srcOffset = 0; + pageData->regions[0].dstOffset = 0; + pageData->regions[0].length = BLCKSZ; + pageData->regionsCount = 1; + pageData->dataLen = 0; + } + + static Region * + findRegion(PageData *pageData, OffsetNumber offset) + { + Region *region; + Assert(offset >= 0 && offset < BLCKSZ); + region = bsearch(&offset, pageData->regions, pageData->regionsCount, + sizeof(Region), regionOffsetCmp); + + /* We should always find region... */ + if (!region) + elog(ERROR, "Can't find region"); + + return region; + } + + static void + memoryMove(PageData *pageData, OffsetNumber dstOffset, OffsetNumber srcOffset, + OffsetNumber length) + { + OffsetNumber curOffset, curLength, leftLength = 0; + Region newRegions[MAX_REGIONS], *newRegion, *srcRegion, *dstRegionLeft, *dstRegionRight; + bool leftAdjacent, rightAdjacent; + int newRegionsCount, leftIndex, rightIndex, shift; + #ifdef DEBUG_PRINT + int i; + #endif + + if (pageData->overflow) + return; + + srcRegion = findRegion(pageData, srcOffset); + + /* Make new regions */ + curOffset = dstOffset; + curLength = length; + newRegion = newRegions; + while (curLength > 0) + { + OffsetNumber shift = srcOffset - srcRegion->dstOffset; + + newRegion->dstOffset = curOffset; + newRegion->srcOffset = srcRegion->srcOffset + shift; + newRegion->length = Min(srcRegion->length - shift, curLength); + srcOffset += newRegion->length; + curLength -= newRegion->length; + srcRegion++; + newRegion++; + } + newRegionsCount = newRegion - newRegions; + + /* Check left region */ + dstRegionLeft = findRegion(pageData, dstOffset > 0 ? dstOffset - 1 : 0); + newRegion = &newRegions[0]; + shift = dstOffset - dstRegionLeft->dstOffset; + + if (shift == 0) + { + leftAdjacent = true; + } + else if (newRegion->srcOffset == dstRegionLeft->srcOffset + shift) + { + leftAdjacent = true; + newRegion->dstOffset -= shift; + newRegion->srcOffset -= shift; + newRegion->length += shift; + } + else + { + leftAdjacent = false; + leftLength = shift; + } + leftIndex = dstRegionLeft - pageData->regions + (leftAdjacent ? 0 : 1); + + /* Check right region */ + dstRegionRight = findRegion(pageData, dstOffset + length < BLCKSZ ? dstOffset + length : BLCKSZ - 1); + newRegion = &newRegions[newRegionsCount - 1]; + shift = (dstRegionRight->dstOffset + dstRegionRight->length) - + (dstOffset + length); + + if (shift == 0) + { + rightAdjacent = true; + } + else if (newRegion->srcOffset + newRegion->length + shift == + dstRegionRight->srcOffset + dstRegionRight->length) + { + rightAdjacent = true; + newRegion->length += shift; + } + else + { + Region *tmp = dstRegionRight; + + if (!leftAdjacent) + { + newRegionsCount++; + tmp = &newRegions[newRegionsCount - 1]; + *tmp = *dstRegionRight; + rightAdjacent = true; + } + else + { + rightAdjacent = false; + } + tmp->srcOffset += (tmp->length - shift); + tmp->dstOffset += (tmp->length - shift); + tmp->length = shift; + } + if (!leftAdjacent) + dstRegionLeft->length = leftLength; + rightIndex = dstRegionRight - pageData->regions + (rightAdjacent ? 1 : 0); + + /* Move data */ + shift = newRegionsCount - (rightIndex - leftIndex); + if (pageData->regionsCount + shift > MAX_REGIONS) + { + initPageData(pageData, pageData->buffer); + pageData->overflow = true; + return; + } + + #ifdef DEBUG_PRINT + for (i = 0; i < pageData->regionsCount; i++) + { + Region *region = &pageData->regions[i]; + printf("old %d %d %d %d\n", i, region->dstOffset, region->srcOffset, region->length); + } + for (i = 0; i < newRegionsCount; i++) + { + Region *region = &newRegions[i]; + printf("new %d %d %d %d\n", i, region->dstOffset, region->srcOffset, region->length); + } + printf("idx %d %d\n", leftIndex, rightIndex); + #endif + + memmove(&pageData->regions[rightIndex + shift], + &pageData->regions[rightIndex], + sizeof(Region) * (pageData->regionsCount - rightIndex)); + pageData->regionsCount += shift; + memcpy(&pageData->regions[leftIndex], + newRegions, + sizeof(Region) * newRegionsCount); + } + + #ifdef DEBUG_PRINT + static void + printPageData(PageData *pageData) + { + int i; + for (i = 0; i < pageData->regionsCount; i++) + { + Region *region = &pageData->regions[i]; + printf("cur %d %d %d %d\n", i, region->dstOffset, region->srcOffset, region->length); + } + } + + Datum + check_move(PG_FUNCTION_ARGS) + { + PageData pageData; + initPageData(&pageData, false); + printPageData(&pageData); + memoryMove(&pageData, 0, 4096, 4096); + printPageData(&pageData); + memoryMove(&pageData, 0, 2048, 2048); + printPageData(&pageData); + memoryMove(&pageData, 0, 1024, 1024); + printPageData(&pageData); + PG_RETURN_VOID(); + } + #endif + + static void + writeCopyFlagment(PageData *pageData, OffsetNumber length, Pointer source) + { + Pointer ptr = pageData->data + pageData->dataLen; + + memcpy(ptr, &length, sizeof(OffsetNumber)); + ptr += sizeof(OffsetNumber); + memcpy(ptr, source, length); + ptr += length; + + pageData->dataLen = ptr - pageData->data; + } + + static void + writeMoveFlagment(PageData *pageData, OffsetNumber length, OffsetNumber source) + { + Pointer ptr = pageData->data + pageData->dataLen; + + length |= MOVE_FLAG; + memcpy(ptr, &length, sizeof(OffsetNumber)); + ptr += sizeof(OffsetNumber); + memcpy(ptr, &source, sizeof(OffsetNumber)); + ptr += sizeof(OffsetNumber); + + pageData->dataLen = ptr - pageData->data; + } + + #define CHECK_SET \ + if (i - match > MATCH_THRESHOLD) \ + { \ + if (notMatch < match) \ + writeCopyFlagment(pageData, match - notMatch, page + notMatch); \ + writeMoveFlagment(pageData, i - match, region->srcOffset + regionOffset - (i - match)); \ + notMatch = i; \ + } \ + + static void + writeDifferentialData(PageData *pageData) + { + Page page = BufferGetPage(pageData->buffer); + Pointer image = pageData->image; + Region *region = pageData->regions; + OffsetNumber i, regionOffset = 0; + OffsetNumber notMatch = 0, match = 0; + + for (i = 0; i < BLCKSZ; i++) + { + if (regionOffset >= region->length) + { + CHECK_SET; + match = i; + region++; + regionOffset = 0; + } + + if (page[i] != image[region->srcOffset + regionOffset]) + { + CHECK_SET; + match = i + 1; + } + + regionOffset++; + } + CHECK_SET; + if (notMatch < BLCKSZ) + { + writeCopyFlagment(pageData, BLCKSZ - notMatch, page + notMatch); + } + } + + void + GenericXLogStart(Relation index) + { + int i; + + if (genericXlogStatus != GXLOG_NOT_STARTED) + elog(ERROR, "GenericXLogStart: generic xlog is already started"); + + genericXlogStatus = RelationNeedsWAL(index) ? GXLOG_LOGGED : GXLOG_UNLOGGED; + + for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++) + { + pages[i].buffer = InvalidBuffer; + } + } + + Page + GenericXLogRegister(Buffer buffer, bool isNew) + { + int block_id; + + if (genericXlogStatus == GXLOG_NOT_STARTED) + elog(ERROR, "GenericXLogRegister: generic xlog isn't started"); + + for (block_id = 0; block_id < MAX_GENERIC_XLOG_PAGES; block_id++) + { + if (BufferIsInvalid(pages[block_id].buffer)) + { + initPageData(&pages[block_id], buffer); + pages[block_id].fullImage = isNew; + return (Page)pages[block_id].image; + } + else if (pages[block_id].buffer == buffer) + { + elog(ERROR, "GenericXLogRegister: duplicate buffer %d", buffer); + } + } + + elog(ERROR, "GenericXLogRegister: maximum number of %d buffers is exceeded", + MAX_GENERIC_XLOG_PAGES); + + /* keep compiler quiet */ + return NULL; + } + + void + GenericXLogMemmove(Pointer dst, Pointer src, OffsetNumber len) + { + Page page; + int i, block_id = -1; + + if (genericXlogStatus == GXLOG_NOT_STARTED) + elog(ERROR, "GenericXLogMemmove: generic xlog isn't started"); + + /* Find block */ + for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++) + { + page = pages[i].image; + + if (dst >= page && dst < page + BLCKSZ) + { + block_id = i; + break; + } + } + + /* Check block is found */ + if (block_id < 0) + elog(ERROR, "GenericXLogMemmove: page not found"); + + /* Check both source and destination of memmove are inside of page */ + if (src < page || src + len > page + BLCKSZ) + elog(ERROR, "GenericXLogMemmove: source is outside of page"); + if (dst < page || dst + len > page + BLCKSZ) + elog(ERROR, "GenericXLogMemmove: desitnation is outside of page"); + + if (pages[block_id].fullImage) + return; + + memoryMove(&pages[block_id], dst - page, src - page, len); + memmove(dst, src, len); + } + + #ifdef DEBUG_PRINT + static void + printDataBlock(Pointer ptr, Pointer end) + { + OffsetNumber length, offset = 0, source; + + while (ptr < end) + { + memcpy(&length, ptr, sizeof(length)); + ptr += sizeof(length); + + if (length & MOVE_FLAG) + { + length &= LENGTH_MASK; + memcpy(&source, ptr, sizeof(source)); + ptr += sizeof(source); + printf("\t%d: move %d from %d\n", offset, length, source); + } + else + { + OffsetNumber i; + printf("\t%d: set %d -", offset, length); + for (i = 0; i < length; i++) + { + printf(" %02X", (uint8)ptr[i]); + } + printf("\n"); + ptr += length; + } + offset += length; + } + } + #endif + + XLogRecPtr + GenericXLogFinish(void) + { + XLogRecPtr lsn = InvalidXLogRecPtr; + int i; + + if (genericXlogStatus == GXLOG_LOGGED) + { + START_CRIT_SECTION(); + XLogBeginInsert(); + + for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++) + { + char tmp[BLCKSZ]; + + if (BufferIsInvalid(pages[i].buffer)) + continue; + + 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) + { + XLogRegisterBuffer(i, pages[i].buffer, REGBUF_FORCE_IMAGE); + } + else + { + XLogRegisterBuffer(i, pages[i].buffer, REGBUF_STANDARD); + writeDifferentialData(&pages[i]); + XLogRegisterBufData(i, pages[i].data, pages[i].dataLen); + } + } + + lsn = XLogInsert(RM_GENERIC_ID, 0); + 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) + { + 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 + { + elog(ERROR, "GenericXLogFinish: generic xlog isn't started"); + } + + #ifdef DEBUG_PRINT + for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++) + { + if (BufferIsInvalid(pages[i].buffer)) + continue; + printf("Block %08X\n", BufferGetBlockNumber(pages[i].buffer)); + if (pages[i].fullImage) + printf("full image is forced"); + else + printDataBlock(pages[i].data, pages[i].data + pages[i].dataLen); + } + #endif + + genericXlogStatus = GXLOG_NOT_STARTED; + + return lsn; + } + + void + GenericXLogAbort(void) + { + if (genericXlogStatus == GXLOG_NOT_STARTED) + elog(ERROR, "GenericXLogMemmove: generic xlog isn't started"); + + genericXlogStatus = GXLOG_NOT_STARTED; + } + + static void + applyPageRedo(Page page, Page image, Pointer data, Size dataSize) + { + OffsetNumber length, offset = 0, source; + Pointer ptr = data, end = data + dataSize; + + while (ptr < end) + { + memcpy(&length, ptr, sizeof(length)); + ptr += sizeof(length); + + if (length & MOVE_FLAG) + { + length &= LENGTH_MASK; + memcpy(&source, ptr, sizeof(source)); + ptr += sizeof(source); + + memcpy(page + offset, image + source, length); + } + else + { + memcpy(page + offset, ptr, length); + + ptr += length; + } + offset += length; + } + } + + void + generic_redo(XLogReaderState *record) + { + uint8 block_id; + Buffer buffers[MAX_GENERIC_XLOG_PAGES] = {InvalidBuffer}; + XLogRecPtr lsn = record->EndRecPtr; + char image[BLCKSZ]; + + Assert(record->max_block_id < MAX_GENERIC_XLOG_PAGES); + + #ifdef DEBUG_PRINT + elog(LOG, "Generic redo: %d (pid = %d)!", record->max_block_id, MyProcPid); + #endif + + 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]); + #ifdef DEBUG_PRINT + elog(LOG, "%d - %d", block_id, action); + #endif + if (action == BLK_NEEDS_REDO) + { + Pointer blockData; + Size blockDataSize; + Page page; + + page = BufferGetPage(buffers[block_id]); + memcpy(image, page, BLCKSZ); + + blockData = XLogRecGetBlockData(record, block_id, &blockDataSize); + + #ifdef DEBUG_PRINT + printDataBlock(blockData, blockData + blockDataSize); + #endif + + applyPageRedo(page, image, blockData, blockDataSize); + + PageSetLSN(page, lsn); + MarkBufferDirty(buffers[block_id]); + } + #ifdef DEBUG_PRINT + if (action == BLK_RESTORED) + { + int i; + RestoreBlockImage(record, block_id, image); + printf("Restored, image: "); + for (i = 0; i < BLCKSZ; i++) + { + printf("%02X ", (uint8)image[i]); + } + printf("\n"); + } + #endif + } + + 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 88c3a49..2d69dc2 *** a/src/backend/replication/logical/decode.c --- b/src/backend/replication/logical/decode.c *************** LogicalDecodingProcessRecord(LogicalDeco *** 135,140 **** --- 135,141 ---- case RM_BRIN_ID: case RM_COMMIT_TS_ID: case RM_REPLORIGIN_ID: + case RM_GENERIC_ID: break; case RM_NEXT_ID: elog(ERROR, "unexpected RM_NEXT_ID rmgr_id: %u", (RmgrIds) XLogRecGetRmid(buf.record)); 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 ...45c593d *** a/src/include/access/generic_xlog.h --- b/src/include/access/generic_xlog.h *************** *** 0 **** --- 1,32 ---- + /*------------------------------------------------------------------------- + * + * generic_xlog.h + * POSTGRES generic XLOG definitions. + * + * + * Portions Copyright (c) 1996-2014, 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" + + extern void GenericXLogStart(Relation index); + extern Page GenericXLogRegister(Buffer buffer, bool isNew); + extern void GenericXLogMemmove(Pointer dst, Pointer src, OffsetNumber len); + extern XLogRecPtr GenericXLogFinish(void); + extern void GenericXLogAbort(void); + + 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)