From 65578341af1ae50e52e0f45e691ce88ad5a1b9b1 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Fri, 1 Apr 2016 12:21:48 +0300 Subject: [PATCH] Add Generic WAL interface This interface is designed to give an access to WAL for extensions which could implement new access method, for example. Previously it was impossible because restoring from custom WAL would need to access system catalog to find a redo custom function. This patch suggests generic way to describe changes on page with standart layout. Bump XLOG_PAGE_MAGIC because of new record type. Author: Alexander Korotkov with a help of Petr Jelinek, Markus Nullmeier and minor editorization by my Reviewers: Petr Jelinek, Alvaro Herrera, Teodor Sigaev, Jim Nasby, Michael Paquier --- doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/generic-wal.sgml | 141 +++++++ doc/src/sgml/postgres.sgml | 1 + src/backend/access/rmgrdesc/Makefile | 6 +- src/backend/access/rmgrdesc/genericdesc.c | 58 +++ src/backend/access/transam/Makefile | 4 +- src/backend/access/transam/generic_xlog.c | 431 ++++++++++++++++++++++ src/backend/access/transam/rmgr.c | 1 + src/backend/replication/logical/decode.c | 1 + src/bin/pg_xlogdump/.gitignore | 1 + src/bin/pg_xlogdump/rmgrdesc.c | 1 + src/include/access/generic_xlog.h | 42 +++ src/include/access/rmgrlist.h | 1 + src/include/access/xlog_internal.h | 2 +- 14 files changed, 685 insertions(+), 6 deletions(-) create mode 100644 doc/src/sgml/generic-wal.sgml create mode 100644 src/backend/access/rmgrdesc/genericdesc.c create mode 100644 src/backend/access/transam/generic_xlog.c create mode 100644 src/include/access/generic_xlog.h diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 30adecee36..9046f50628 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -100,6 +100,7 @@ + diff --git a/doc/src/sgml/generic-wal.sgml b/doc/src/sgml/generic-wal.sgml new file mode 100644 index 0000000000..6655f22f3a --- /dev/null +++ b/doc/src/sgml/generic-wal.sgml @@ -0,0 +1,141 @@ + + + + Generic WAL records + + + Despite all built-in access methods and WAL-logged modules having their own + types of WAL records, there is also a generic WAL record type, which describes + changes to pages in a generic way. This is useful for extensions that + provide custom access methods, because they cannot register their own + WAL redo routines. + + + + The API for contructing generic WAL records is defined in + generic_xlog.h and implemented in generic_xlog.c. + Each generic WAL record must be constructed by following these steps: + + + + + state = GenericXLogStart(relation) — start + construction of a generic xlog record for the given relation. + + + + + + page = GenericXLogRegister(state, buffer, isNew) — + register one or more buffers (one at a time) for the current generic + xlog record. This function returns a copy of the page image, where + modifications can be made. The second argument indicates if the page + is new (eventually, this will result in a full page image being put into + the xlog record). + + + + + + Apply modifications to page images obtained in the previous step. + + + + + + GenericXLogAbort(state) — finish construction of + a generic xlog record. + + + + + + + The xlog record construction can be canceled between any of the above + steps by calling GenericXLogAbort(). This will discard all + changes to the page image copies. + + + + Please note the following points when constructing generic xlog records: + + + + No direct modifications of page images are allowed! All modifications + must be done in copies acquired from GenericXLogRegister(). + In other words, code which makes generic xlog records must never call + BufferGetPage(). + + + + + + Registrations of buffers (step 2) and modifications of page images + (step 3) can be mixed freely, i.e., both steps may be repeated in any + sequence. The only restriction is that you can modify a page image + only after the registration of the corresponding buffer. + + + + + + After registration, the buffer can also be unregistered by calling + GenericXLogUnregister(buffer). In this case, the changes + made to that 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. + + + + + + The maximum number of buffers that can be simultaneously registered + for a generic xlog is MAX_GENERIC_XLOG_PAGES. An error will + be thrown if this limit is exceeded. + + + + + Since you modify copies of page images, GenericXLogStart() + does not start a critical section. Thus, you can do memory allocation, + error throwing, etc. between GenericXLogStart() and + GenericXLogFinish(). The actual critical section is present + inside GenericXLogFinish(). + + + + + GenericXLogFinish() takes care of marking buffers as dirty + and setting their LSNs. You do not need to do this explicitly. + + + + + For unlogged relations, everything works the same except there is no + WAL record produced. Thus, you typically do not need to do any explicit + checks for unlogged relations. + + + + + If a registered buffer is not new, the generic xlog record contains + a delta between the old and the new page images. This delta is produced + using per byte comparison. The current delta mechanism is not effective + for moving data within a page and may be improved in the future. + + + + + The generic xlog redo function will acquire exclusive locks to buffers + in the same order as they were registered. After redoing all changes, + the locks will be released in the same order. + + + + + diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index 7e82cdc3b1..0346d367e5 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -247,6 +247,7 @@ &custom-scan; &geqo; &indexam; + &generic-wal; &gist; &spgist; &gin; diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile index c72a1f245d..c0e38fdf17 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -8,9 +8,9 @@ subdir = src/backend/access/rmgrdesc 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 \ +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 0000000000..caa9a03648 --- /dev/null +++ b/src/backend/access/rmgrdesc/genericdesc.c @@ -0,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 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); + } + + 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 index 94455b23f7..16fbe47269 100644 --- a/src/backend/access/transam/Makefile +++ b/src/backend/access/transam/Makefile @@ -12,8 +12,8 @@ subdir = src/backend/access/transam 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 \ +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 0000000000..e62179d2fb --- /dev/null +++ b/src/backend/access/transam/generic_xlog.c @@ -0,0 +1,431 @@ +/*------------------------------------------------------------------------- + * + * 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" + +/*------------------------------------------------------------------------- + * Internally, a delta between pages consists of a set of fragments. Each + * fragment represents changes made in a given region of a page. A fragment + * is made up as follows: + * + * - offset of page region (OffsetNumber) + * - length of page region (OffsetNumber) + * - data - the data to place into the region ('length' number of bytes) + * + * Unchanged regions of a page are not represented in its delta. As a + * result, a delta can be more compact than the full page image. But having + * an unchanged region in the middle of two fragments that is smaller than + * the fragment header (offset and length) does not pay off in terms of the + * overall size of the delta. For this reason, we break fragments only if + * the unchanged region is bigger than MATCH_THRESHOLD. + * + * The worst case for delta sizes occurs when we did not find any unchanged + * region in the page. The size of the delta will be the size of the page plus + * the size of the fragment header in that case. + *------------------------------------------------------------------------- + */ +#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 a full image of this page? */ +} PageData; + +/* State of generic xlog record construction */ +struct GenericXLogState +{ + bool isLogged; + 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 if we have enough 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 match. We do not care + * about data in the unallocated area between pd_lower and pd_upper. + * We assume the unallocated area to expand with unmatched bytes. + * Bytes inside the 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 potentially part of a 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 is 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); + if (memcmp(tmp, page, pageLower) + || memcmp(tmp + pageUpper, page + pageUpper, BLCKSZ - pageUpper)) + elog(ERROR, "result of generic xlog apply does not match"); + } +#endif +} + +/* + * Start new generic xlog record. + */ +GenericXLogState * +GenericXLogStart(Relation relation) +{ + int i; + GenericXLogState *state; + + state = (GenericXLogState *) palloc(sizeof(GenericXLogState)); + + state->isLogged = RelationNeedsWAL(relation); + for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++) + state->pages[i].buffer = InvalidBuffer; + + return state; +} + +/* + * Register new buffer for generic xlog record. + */ +Page +GenericXLogRegister(GenericXLogState *state, Buffer buffer, bool isNew) +{ + int block_id; + + /* Place new buffer to unused slot in array */ + for (block_id = 0; block_id < MAX_GENERIC_XLOG_PAGES; block_id++) + { + PageData *page = &state->pages[block_id]; + if (BufferIsInvalid(page->buffer)) + { + page->buffer = buffer; + memcpy(page->image, BufferGetPage(buffer), BLCKSZ); + page->dataLen = 0; + page->fullImage = isNew; + return (Page)page->image; + } + else if (page->buffer == buffer) + { + /* + * Buffer is already registered. Just return the image, which is + * already prepared. + */ + return (Page)page->image; + } + } + + elog(ERROR, "maximum number of %d generic xlog buffers is exceeded", + MAX_GENERIC_XLOG_PAGES); + + /* keep compiler quiet */ + return NULL; +} + +/* + * Unregister particular buffer for generic xlog record. + */ +void +GenericXLogUnregister(GenericXLogState *state, Buffer buffer) +{ + int block_id; + + /* Find block in array to unregister */ + for (block_id = 0; block_id < MAX_GENERIC_XLOG_PAGES; block_id++) + { + if (state->pages[block_id].buffer == buffer) + { + /* + * Preserve order of pages in array because it could matter for + * concurrency. + */ + memmove(&state->pages[block_id], &state->pages[block_id + 1], + (MAX_GENERIC_XLOG_PAGES - block_id - 1) * sizeof(PageData)); + state->pages[MAX_GENERIC_XLOG_PAGES - 1].buffer = InvalidBuffer; + return; + } + } + + elog(ERROR, "registered generic xlog buffer not found"); +} + +/* + * Put all changes in registered buffers to generic xlog record. + */ +XLogRecPtr +GenericXLogFinish(GenericXLogState *state) +{ + XLogRecPtr lsn = InvalidXLogRecPtr; + int i; + + if (state->isLogged) + { + /* Logged relation: make xlog record in critical section. */ + XLogBeginInsert(); + + START_CRIT_SECTION(); + + for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++) + { + char tmp[BLCKSZ]; + PageData *page = &state->pages[i]; + + if (BufferIsInvalid(page->buffer)) + continue; + + /* Swap current and saved page image. */ + memcpy(tmp, page->image, BLCKSZ); + memcpy(page->image, BufferGetPage(page->buffer), BLCKSZ); + memcpy(BufferGetPage(page->buffer), tmp, BLCKSZ); + + if (page->fullImage) + { + /* A full page image does not require anything special */ + XLogRegisterBuffer(i, page->buffer, REGBUF_FORCE_IMAGE); + } + else + { + /* + * In normal mode, calculate delta and write it as data + * associated with this page. + */ + XLogRegisterBuffer(i, page->buffer, REGBUF_STANDARD); + writeDelta(page); + XLogRegisterBufData(i, page->data, page->dataLen); + } + } + + /* Insert xlog record */ + lsn = XLogInsert(RM_GENERIC_ID, 0); + + /* Set LSN and mark buffers dirty */ + for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++) + { + PageData *page = &state->pages[i]; + + if (BufferIsInvalid(page->buffer)) + continue; + PageSetLSN(BufferGetPage(page->buffer), lsn); + MarkBufferDirty(page->buffer); + } + END_CRIT_SECTION(); + } + else + { + /* Unlogged relation: skip xlog-related stuff */ + START_CRIT_SECTION(); + for (i = 0; i < MAX_GENERIC_XLOG_PAGES; i++) + { + PageData *page = &state->pages[i]; + + if (BufferIsInvalid(page->buffer)) + continue; + memcpy(BufferGetPage(page->buffer), page->image, BLCKSZ); + MarkBufferDirty(page->buffer); + } + END_CRIT_SECTION(); + } + + pfree(state); + + return lsn; +} + +/* + * Abort generic xlog record. + */ +void +GenericXLogAbort(GenericXLogState *state) +{ + pfree(state); +} + +/* + * 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); + + /* Iterate 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 index 7c4d773ce0..7b38c16f52 100644 --- a/src/backend/access/transam/rmgr.c +++ b/src/backend/access/transam/rmgr.c @@ -11,6 +11,7 @@ #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 index f0bc67c7f5..7781ebcae0 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -143,6 +143,7 @@ LogicalDecodingProcessRecord(LogicalDecodingContext *ctx, XLogReaderState *recor 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 index eebaf3039f..33a1acfd2c 100644 --- a/src/bin/pg_xlogdump/.gitignore +++ b/src/bin/pg_xlogdump/.gitignore @@ -4,6 +4,7 @@ /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 index f9cd395af6..cff7e59f34 100644 --- a/src/bin/pg_xlogdump/rmgrdesc.c +++ b/src/bin/pg_xlogdump/rmgrdesc.c @@ -11,6 +11,7 @@ #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 0000000000..0be0591a24 --- /dev/null +++ b/src/include/access/generic_xlog.h @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * 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 "access/xloginsert.h" +#include "storage/bufpage.h" +#include "utils/rel.h" + +#define MAX_GENERIC_XLOG_PAGES XLR_NORMAL_MAX_BLOCK_ID + +/* state of generic xlog record construction */ +struct GenericXLogState; +typedef struct GenericXLogState GenericXLogState; + +/* API for construction of generic xlog records */ +extern GenericXLogState *GenericXLogStart(Relation relation); +extern Page GenericXLogRegister(GenericXLogState *state, Buffer buffer, + bool isNew); +extern void GenericXLogUnregister(GenericXLogState *state, Buffer buffer); +extern XLogRecPtr GenericXLogFinish(GenericXLogState *state); +extern void GenericXLogAbort(GenericXLogState *state); + +/* 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 index fab912d301..3cfe6f7b54 100644 --- a/src/include/access/rmgrlist.h +++ b/src/include/access/rmgrlist.h @@ -45,3 +45,4 @@ PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_identify, spg_xlog_start 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) diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index aa2f074201..749060f9c7 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -31,7 +31,7 @@ /* * Each page of XLOG file has a header like this: */ -#define XLOG_PAGE_MAGIC 0xD089 /* can be used as WAL version indicator */ +#define XLOG_PAGE_MAGIC 0xD090 /* can be used as WAL version indicator */ typedef struct XLogPageHeaderData { -- 2.40.0