v18-0003-Move-pg_get_wal_record_info-functionality-from-p.patch
application/octet-stream
Filename: v18-0003-Move-pg_get_wal_record_info-functionality-from-p.patch
Type: application/octet-stream
Part: 2
Patch
Format: format-patch
Series: patch v18-0003
Subject: Move pg_get_wal_record_info functionality from pg_walinspect to core
| File | + | − |
|---|---|---|
| contrib/pg_walinspect/Makefile | 1 | 1 |
| contrib/pg_walinspect/meson.build | 1 | 0 |
| contrib/pg_walinspect/pg_walinspect--1.1--1.2.sql | 31 | 0 |
| contrib/pg_walinspect/pg_walinspect.c | 3 | 177 |
| contrib/pg_walinspect/pg_walinspect.control | 1 | 1 |
| src/backend/access/transam/xlogfuncs.c | 50 | 0 |
| src/backend/access/transam/xlogutils.c | 173 | 0 |
| src/backend/catalog/system_functions.sql | 4 | 0 |
| src/include/access/xlogutils.h | 6 | 0 |
| src/include/catalog/pg_proc.dat | 9 | 0 |
| src/test/regress/expected/misc_functions.out | 18 | 0 |
| src/test/regress/sql/misc_functions.sql | 16 | 0 |
From e1cbde65108376c61377f12089bdc3990b53330e Mon Sep 17 00:00:00 2001
From: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Date: Tue, 1 Aug 2023 14:04:04 +0000
Subject: [PATCH v18 3/4] Move pg_get_wal_record_info functionality from
pg_walinspect to core
Upgrade of publications required pg_get_wal_record_info to check that there are
no WAL records other than CHECKPOINT_SHUTDOWN WAL record to be consumed. Hence
moved pg_get_wal_record_info functionality as pg_get_wal_record_content
to core so that it can be called from pg_upgrade.
---
contrib/pg_walinspect/Makefile | 2 +-
contrib/pg_walinspect/meson.build | 1 +
.../pg_walinspect/pg_walinspect--1.1--1.2.sql | 31 +++
contrib/pg_walinspect/pg_walinspect.c | 180 +-----------------
contrib/pg_walinspect/pg_walinspect.control | 2 +-
src/backend/access/transam/xlogfuncs.c | 50 +++++
src/backend/access/transam/xlogutils.c | 173 +++++++++++++++++
src/backend/catalog/system_functions.sql | 4 +
src/include/access/xlogutils.h | 6 +
src/include/catalog/pg_proc.dat | 9 +
src/test/regress/expected/misc_functions.out | 18 ++
src/test/regress/sql/misc_functions.sql | 16 ++
12 files changed, 313 insertions(+), 179 deletions(-)
create mode 100644 contrib/pg_walinspect/pg_walinspect--1.1--1.2.sql
diff --git a/contrib/pg_walinspect/Makefile b/contrib/pg_walinspect/Makefile
index 22090f7716..5cc7d81b42 100644
--- a/contrib/pg_walinspect/Makefile
+++ b/contrib/pg_walinspect/Makefile
@@ -7,7 +7,7 @@ OBJS = \
PGFILEDESC = "pg_walinspect - functions to inspect contents of PostgreSQL Write-Ahead Log"
EXTENSION = pg_walinspect
-DATA = pg_walinspect--1.0.sql pg_walinspect--1.0--1.1.sql
+DATA = pg_walinspect--1.0.sql pg_walinspect--1.0--1.1.sql pg_walinspect--1.1--1.2.sql
REGRESS = pg_walinspect oldextversions
diff --git a/contrib/pg_walinspect/meson.build b/contrib/pg_walinspect/meson.build
index 80059f6119..8f7a99a493 100644
--- a/contrib/pg_walinspect/meson.build
+++ b/contrib/pg_walinspect/meson.build
@@ -20,6 +20,7 @@ install_data(
'pg_walinspect.control',
'pg_walinspect--1.0.sql',
'pg_walinspect--1.0--1.1.sql',
+ 'pg_walinspect--1.1--1.2.sql',
kwargs: contrib_data_args,
)
diff --git a/contrib/pg_walinspect/pg_walinspect--1.1--1.2.sql b/contrib/pg_walinspect/pg_walinspect--1.1--1.2.sql
new file mode 100644
index 0000000000..48e2da3034
--- /dev/null
+++ b/contrib/pg_walinspect/pg_walinspect--1.1--1.2.sql
@@ -0,0 +1,31 @@
+/* contrib/pg_walinspect/pg_walinspect--1.1--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_walinspect UPDATE TO '1.2'" to load this file. \quit
+
+-- The function is now in the backend and callers should update to use those.
+
+ALTER EXTENSION pg_walinspect DROP FUNCTION pg_get_wal_record_info;
+DROP FUNCTION pg_get_wal_record_info(pg_lsn);
+
+--
+-- pg_get_wal_record_info()
+--
+CREATE FUNCTION pg_get_wal_record_info(IN in_lsn pg_lsn,
+ OUT start_lsn pg_lsn,
+ OUT end_lsn pg_lsn,
+ OUT prev_lsn pg_lsn,
+ OUT xid xid,
+ OUT resource_manager text,
+ OUT record_type text,
+ OUT record_length int4,
+ OUT main_data_length int4,
+ OUT fpi_length int4,
+ OUT description text,
+ OUT block_ref text
+)
+AS 'pg_get_wal_record_content'
+LANGUAGE INTERNAL STRICT PARALLEL SAFE;
+
+REVOKE EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) TO pg_read_server_files;
diff --git a/contrib/pg_walinspect/pg_walinspect.c b/contrib/pg_walinspect/pg_walinspect.c
index 796a74f322..8cba14e789 100644
--- a/contrib/pg_walinspect/pg_walinspect.c
+++ b/contrib/pg_walinspect/pg_walinspect.c
@@ -39,11 +39,6 @@ PG_FUNCTION_INFO_V1(pg_get_wal_stats);
PG_FUNCTION_INFO_V1(pg_get_wal_stats_till_end_of_wal);
static void ValidateInputLSNs(XLogRecPtr start_lsn, XLogRecPtr *end_lsn);
-static XLogRecPtr GetCurrentLSN(void);
-static XLogReaderState *InitXLogReaderState(XLogRecPtr lsn);
-static XLogRecord *ReadNextXLogRecord(XLogReaderState *xlogreader);
-static void GetWALRecordInfo(XLogReaderState *record, Datum *values,
- bool *nulls, uint32 ncols);
static void GetWALRecordsInfo(FunctionCallInfo fcinfo,
XLogRecPtr start_lsn,
XLogRecPtr end_lsn);
@@ -62,178 +57,6 @@ static void GetWalStats(FunctionCallInfo fcinfo,
static void GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record,
bool show_data);
-/*
- * Return the LSN up to which the server has WAL.
- */
-static XLogRecPtr
-GetCurrentLSN(void)
-{
- XLogRecPtr curr_lsn;
-
- /*
- * We determine the current LSN of the server similar to how page_read
- * callback read_local_xlog_page_no_wait does.
- */
- if (!RecoveryInProgress())
- curr_lsn = GetFlushRecPtr(NULL);
- else
- curr_lsn = GetXLogReplayRecPtr(NULL);
-
- Assert(!XLogRecPtrIsInvalid(curr_lsn));
-
- return curr_lsn;
-}
-
-/*
- * Initialize WAL reader and identify first valid LSN.
- */
-static XLogReaderState *
-InitXLogReaderState(XLogRecPtr lsn)
-{
- XLogReaderState *xlogreader;
- ReadLocalXLogPageNoWaitPrivate *private_data;
- XLogRecPtr first_valid_record;
-
- /*
- * Reading WAL below the first page of the first segments isn't allowed.
- * This is a bootstrap WAL page and the page_read callback fails to read
- * it.
- */
- if (lsn < XLOG_BLCKSZ)
- ereport(ERROR,
- (errmsg("could not read WAL at LSN %X/%X",
- LSN_FORMAT_ARGS(lsn))));
-
- private_data = (ReadLocalXLogPageNoWaitPrivate *)
- palloc0(sizeof(ReadLocalXLogPageNoWaitPrivate));
-
- xlogreader = XLogReaderAllocate(wal_segment_size, NULL,
- XL_ROUTINE(.page_read = &read_local_xlog_page_no_wait,
- .segment_open = &wal_segment_open,
- .segment_close = &wal_segment_close),
- private_data);
-
- if (xlogreader == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("out of memory"),
- errdetail("Failed while allocating a WAL reading processor.")));
-
- /* first find a valid recptr to start from */
- first_valid_record = XLogFindNextRecord(xlogreader, lsn);
-
- if (XLogRecPtrIsInvalid(first_valid_record))
- ereport(ERROR,
- (errmsg("could not find a valid record after %X/%X",
- LSN_FORMAT_ARGS(lsn))));
-
- return xlogreader;
-}
-
-/*
- * Read next WAL record.
- *
- * By design, to be less intrusive in a running system, no slot is allocated
- * to reserve the WAL we're about to read. Therefore this function can
- * encounter read errors for historical WAL.
- *
- * We guard against ordinary errors trying to read WAL that hasn't been
- * written yet by limiting end_lsn to the flushed WAL, but that can also
- * encounter errors if the flush pointer falls in the middle of a record. In
- * that case we'll return NULL.
- */
-static XLogRecord *
-ReadNextXLogRecord(XLogReaderState *xlogreader)
-{
- XLogRecord *record;
- char *errormsg;
-
- record = XLogReadRecord(xlogreader, &errormsg);
-
- if (record == NULL)
- {
- ReadLocalXLogPageNoWaitPrivate *private_data;
-
- /* return NULL, if end of WAL is reached */
- private_data = (ReadLocalXLogPageNoWaitPrivate *)
- xlogreader->private_data;
-
- if (private_data->end_of_wal)
- return NULL;
-
- if (errormsg)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read WAL at %X/%X: %s",
- LSN_FORMAT_ARGS(xlogreader->EndRecPtr), errormsg)));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read WAL at %X/%X",
- LSN_FORMAT_ARGS(xlogreader->EndRecPtr))));
- }
-
- return record;
-}
-
-/*
- * Output values that make up a row describing caller's WAL record.
- *
- * This function leaks memory. Caller may need to use its own custom memory
- * context.
- *
- * Keep this in sync with GetWALBlockInfo.
- */
-static void
-GetWALRecordInfo(XLogReaderState *record, Datum *values,
- bool *nulls, uint32 ncols)
-{
- const char *record_type;
- RmgrData desc;
- uint32 fpi_len = 0;
- StringInfoData rec_desc;
- StringInfoData rec_blk_ref;
- int i = 0;
-
- desc = GetRmgr(XLogRecGetRmid(record));
- record_type = desc.rm_identify(XLogRecGetInfo(record));
-
- if (record_type == NULL)
- record_type = psprintf("UNKNOWN (%x)", XLogRecGetInfo(record) & ~XLR_INFO_MASK);
-
- initStringInfo(&rec_desc);
- desc.rm_desc(&rec_desc, record);
-
- if (XLogRecHasAnyBlockRefs(record))
- {
- initStringInfo(&rec_blk_ref);
- XLogRecGetBlockRefInfo(record, false, true, &rec_blk_ref, &fpi_len);
- }
-
- values[i++] = LSNGetDatum(record->ReadRecPtr);
- values[i++] = LSNGetDatum(record->EndRecPtr);
- values[i++] = LSNGetDatum(XLogRecGetPrev(record));
- values[i++] = TransactionIdGetDatum(XLogRecGetXid(record));
- values[i++] = CStringGetTextDatum(desc.rm_name);
- values[i++] = CStringGetTextDatum(record_type);
- values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record));
- values[i++] = UInt32GetDatum(XLogRecGetDataLen(record));
- values[i++] = UInt32GetDatum(fpi_len);
-
- if (rec_desc.len > 0)
- values[i++] = CStringGetTextDatum(rec_desc.data);
- else
- nulls[i++] = true;
-
- if (XLogRecHasAnyBlockRefs(record))
- values[i++] = CStringGetTextDatum(rec_blk_ref.data);
- else
- nulls[i++] = true;
-
- Assert(i == ncols);
-}
-
-
/*
* Output one or more rows in rsinfo tuple store, each describing a single
* block reference from caller's WAL record. (Should only be called with
@@ -454,6 +277,9 @@ pg_get_wal_block_info(PG_FUNCTION_ARGS)
/*
* Get WAL record info.
+ *
+ * Note that this function have been removed in newer versions in 1.2, but they
+ * are kept around for compatibility.
*/
Datum
pg_get_wal_record_info(PG_FUNCTION_ARGS)
diff --git a/contrib/pg_walinspect/pg_walinspect.control b/contrib/pg_walinspect/pg_walinspect.control
index efa3cb2cfe..5f574b865b 100644
--- a/contrib/pg_walinspect/pg_walinspect.control
+++ b/contrib/pg_walinspect/pg_walinspect.control
@@ -1,5 +1,5 @@
# pg_walinspect extension
comment = 'functions to inspect contents of PostgreSQL Write-Ahead Log'
-default_version = '1.1'
+default_version = '1.2'
module_pathname = '$libdir/pg_walinspect'
relocatable = true
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 5044ff0643..71ee1069f6 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -754,3 +754,53 @@ pg_promote(PG_FUNCTION_ARGS)
wait_seconds)));
PG_RETURN_BOOL(false);
}
+
+/*
+ * Get WAL record content.
+ */
+Datum
+pg_get_wal_record_content(PG_FUNCTION_ARGS)
+{
+#define PG_GET_WAL_RECORD_CONTENT_COLS 11
+ Datum result;
+ Datum values[PG_GET_WAL_RECORD_CONTENT_COLS] = {0};
+ bool nulls[PG_GET_WAL_RECORD_CONTENT_COLS] = {0};
+ XLogRecPtr lsn;
+ XLogRecPtr curr_lsn;
+ XLogReaderState *xlogreader;
+ TupleDesc tupdesc;
+ HeapTuple tuple;
+
+ lsn = PG_GETARG_LSN(0);
+ curr_lsn = GetCurrentLSN();
+
+ if (lsn > curr_lsn)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("WAL input LSN must be less than current LSN"),
+ errdetail("Current WAL LSN on the database system is at %X/%X.",
+ LSN_FORMAT_ARGS(curr_lsn))));
+
+ /* Build a tuple descriptor for our result type. */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ xlogreader = InitXLogReaderState(lsn);
+
+ if (!ReadNextXLogRecord(xlogreader))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not read WAL at %X/%X",
+ LSN_FORMAT_ARGS(xlogreader->EndRecPtr))));
+
+ GetWALRecordInfo(xlogreader, values, nulls, PG_GET_WAL_RECORD_CONTENT_COLS);
+
+ pfree(xlogreader->private_data);
+ XLogReaderFree(xlogreader);
+
+ tuple = heap_form_tuple(tupdesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+#undef PG_GET_WAL_RECORD_CONTENT_COLS
+}
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index e174a2a891..f637028b8e 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -28,8 +28,10 @@
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/smgr.h"
+#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/hsearch.h"
+#include "utils/pg_lsn.h"
#include "utils/rel.h"
@@ -1048,3 +1050,174 @@ WALReadRaiseError(WALReadError *errinfo)
errinfo->wre_req)));
}
}
+
+/*
+ * Return the LSN up to which the server has WAL.
+ */
+XLogRecPtr
+GetCurrentLSN(void)
+{
+ XLogRecPtr curr_lsn;
+
+ /*
+ * We determine the current LSN of the server similar to how page_read
+ * callback read_local_xlog_page_no_wait does.
+ */
+ if (!RecoveryInProgress())
+ curr_lsn = GetFlushRecPtr(NULL);
+ else
+ curr_lsn = GetXLogReplayRecPtr(NULL);
+
+ Assert(!XLogRecPtrIsInvalid(curr_lsn));
+
+ return curr_lsn;
+}
+
+/*
+ * Initialize WAL reader and identify first valid LSN.
+ */
+XLogReaderState *
+InitXLogReaderState(XLogRecPtr lsn)
+{
+ XLogReaderState *xlogreader;
+ ReadLocalXLogPageNoWaitPrivate *private_data;
+ XLogRecPtr first_valid_record;
+
+ /*
+ * Reading WAL below the first page of the first segments isn't allowed.
+ * This is a bootstrap WAL page and the page_read callback fails to read
+ * it.
+ */
+ if (lsn < XLOG_BLCKSZ)
+ ereport(ERROR,
+ (errmsg("could not read WAL at LSN %X/%X",
+ LSN_FORMAT_ARGS(lsn))));
+
+ private_data = (ReadLocalXLogPageNoWaitPrivate *)
+ palloc0(sizeof(ReadLocalXLogPageNoWaitPrivate));
+
+ xlogreader = XLogReaderAllocate(wal_segment_size, NULL,
+ XL_ROUTINE(.page_read = &read_local_xlog_page_no_wait,
+ .segment_open = &wal_segment_open,
+ .segment_close = &wal_segment_close),
+ private_data);
+
+ if (xlogreader == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory"),
+ errdetail("Failed while allocating a WAL reading processor.")));
+
+ /* first find a valid recptr to start from */
+ first_valid_record = XLogFindNextRecord(xlogreader, lsn);
+
+ if (XLogRecPtrIsInvalid(first_valid_record))
+ ereport(ERROR,
+ (errmsg("could not find a valid record after %X/%X",
+ LSN_FORMAT_ARGS(lsn))));
+
+ return xlogreader;
+}
+
+/*
+ * Read next WAL record.
+ *
+ * By design, to be less intrusive in a running system, no slot is allocated
+ * to reserve the WAL we're about to read. Therefore this function can
+ * encounter read errors for historical WAL.
+ *
+ * We guard against ordinary errors trying to read WAL that hasn't been
+ * written yet by limiting end_lsn to the flushed WAL, but that can also
+ * encounter errors if the flush pointer falls in the middle of a record. In
+ * that case we'll return NULL.
+ */
+XLogRecord *
+ReadNextXLogRecord(XLogReaderState *xlogreader)
+{
+ XLogRecord *record;
+ char *errormsg;
+
+ record = XLogReadRecord(xlogreader, &errormsg);
+
+ if (record == NULL)
+ {
+ ReadLocalXLogPageNoWaitPrivate *private_data;
+
+ /* return NULL, if end of WAL is reached */
+ private_data = (ReadLocalXLogPageNoWaitPrivate *)
+ xlogreader->private_data;
+
+ if (private_data->end_of_wal)
+ return NULL;
+
+ if (errormsg)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read WAL at %X/%X: %s",
+ LSN_FORMAT_ARGS(xlogreader->EndRecPtr), errormsg)));
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read WAL at %X/%X",
+ LSN_FORMAT_ARGS(xlogreader->EndRecPtr))));
+ }
+
+ return record;
+}
+
+/*
+ * Output values that make up a row describing caller's WAL record.
+ *
+ * This function leaks memory. Caller may need to use its own custom memory
+ * context.
+ *
+ * Keep this in sync with GetWALBlockInfo.
+ */
+void
+GetWALRecordInfo(XLogReaderState *record, Datum *values,
+ bool *nulls, uint32 ncols)
+{
+ const char *record_type;
+ RmgrData desc;
+ uint32 fpi_len = 0;
+ StringInfoData rec_desc;
+ StringInfoData rec_blk_ref;
+ int i = 0;
+
+ desc = GetRmgr(XLogRecGetRmid(record));
+ record_type = desc.rm_identify(XLogRecGetInfo(record));
+
+ if (record_type == NULL)
+ record_type = psprintf("UNKNOWN (%x)", XLogRecGetInfo(record) & ~XLR_INFO_MASK);
+
+ initStringInfo(&rec_desc);
+ desc.rm_desc(&rec_desc, record);
+
+ if (XLogRecHasAnyBlockRefs(record))
+ {
+ initStringInfo(&rec_blk_ref);
+ XLogRecGetBlockRefInfo(record, false, true, &rec_blk_ref, &fpi_len);
+ }
+
+ values[i++] = LSNGetDatum(record->ReadRecPtr);
+ values[i++] = LSNGetDatum(record->EndRecPtr);
+ values[i++] = LSNGetDatum(XLogRecGetPrev(record));
+ values[i++] = TransactionIdGetDatum(XLogRecGetXid(record));
+ values[i++] = CStringGetTextDatum(desc.rm_name);
+ values[i++] = CStringGetTextDatum(record_type);
+ values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record));
+ values[i++] = UInt32GetDatum(XLogRecGetDataLen(record));
+ values[i++] = UInt32GetDatum(fpi_len);
+
+ if (rec_desc.len > 0)
+ values[i++] = CStringGetTextDatum(rec_desc.data);
+ else
+ nulls[i++] = true;
+
+ if (XLogRecHasAnyBlockRefs(record))
+ values[i++] = CStringGetTextDatum(rec_blk_ref.data);
+ else
+ nulls[i++] = true;
+
+ Assert(i == ncols);
+}
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 07c0d89c4f..0186d594fb 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -616,6 +616,8 @@ REVOKE EXECUTE ON FUNCTION pg_backup_stop(boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_create_restore_point(text) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_get_wal_record_content(pg_lsn) FROM public;
+
REVOKE EXECUTE ON FUNCTION pg_switch_wal() FROM public;
REVOKE EXECUTE ON FUNCTION pg_log_standby_snapshot() FROM public;
@@ -726,6 +728,8 @@ REVOKE EXECUTE ON FUNCTION pg_ls_replslotdir(text) FROM PUBLIC;
-- We also set up some things as accessible to standard roles.
--
+GRANT EXECUTE ON FUNCTION pg_get_wal_record_content(pg_lsn) TO pg_read_server_files;
+
GRANT EXECUTE ON FUNCTION pg_ls_logdir() TO pg_monitor;
GRANT EXECUTE ON FUNCTION pg_ls_waldir() TO pg_monitor;
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index 5b77b11f50..95ad84ac6f 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -115,4 +115,10 @@ extern void XLogReadDetermineTimeline(XLogReaderState *state,
extern void WALReadRaiseError(WALReadError *errinfo);
+extern XLogRecPtr GetCurrentLSN(void);
+extern XLogReaderState *InitXLogReaderState(XLogRecPtr lsn);
+extern XLogRecord *ReadNextXLogRecord(XLogReaderState *xlogreader);
+extern void GetWALRecordInfo(XLogReaderState *record, Datum *values,
+ bool *nulls, uint32 ncols);
+
#endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..ffe146ba84 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6489,6 +6489,15 @@
proargnames => '{rm_id, rm_name, rm_builtin}',
prosrc => 'pg_get_wal_resource_managers' },
+
+{ oid => '8045', descr => 'Info of the WAL conent',
+ proname => 'pg_get_wal_record_content', prorows => '1', proretset => 't',
+ provolatile => 's', prorettype => 'record', proargtypes => 'pg_lsn',
+ proallargtypes => '{pg_lsn,pg_lsn,pg_lsn,pg_lsn,xid,text,text,int4,int4,int4,text,text}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{in_lsn,start_lsn,end_lsn,prev_lsn,xid,resource_manager,record_type,record_length,main_data_length,fpi_length,description,block_ref}',
+ prosrc => 'pg_get_wal_record_content' },
+
{ oid => '2621', descr => 'reload configuration files',
proname => 'pg_reload_conf', provolatile => 'v', prorettype => 'bool',
proargtypes => '', prosrc => 'pg_reload_conf' },
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index c669948370..bf2d1a5f9b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -642,3 +642,21 @@ SELECT segment_number > 0 AS ok_segment_number, timeline_id
t | 4294967295
(1 row)
+-- pg_get_wal_record_content
+CREATE TABLE sample_tbl(col1 int, col2 int);
+SELECT pg_current_wal_lsn() AS wal_lsn \gset
+INSERT INTO sample_tbl SELECT * FROM generate_series(1, 2);
+-- Mask DETAIL messages as these could refer to current LSN positions.
+\set VERBOSITY terse
+-- Invalid input LSN.
+SELECT * FROM pg_get_wal_record_content('0/0');
+ERROR: could not read WAL at LSN 0/0
+-- LSNs with the highest value possible.
+SELECT * FROM pg_get_wal_record_content('FFFFFFFF/FFFFFFFF');
+ERROR: WAL input LSN must be less than current LSN
+SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_record_content(:'wal_lsn');
+ ok
+----
+ t
+(1 row)
+
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index b57f01f3e9..4ff19b4927 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -237,3 +237,19 @@ SELECT segment_number > 0 AS ok_segment_number, timeline_id
FROM pg_split_walfile_name('000000010000000100000000');
SELECT segment_number > 0 AS ok_segment_number, timeline_id
FROM pg_split_walfile_name('ffffffFF00000001000000af');
+
+-- pg_get_wal_record_content
+CREATE TABLE sample_tbl(col1 int, col2 int);
+SELECT pg_current_wal_lsn() AS wal_lsn \gset
+INSERT INTO sample_tbl SELECT * FROM generate_series(1, 2);
+
+-- Mask DETAIL messages as these could refer to current LSN positions.
+\set VERBOSITY terse
+
+-- Invalid input LSN.
+SELECT * FROM pg_get_wal_record_content('0/0');
+
+-- LSNs with the highest value possible.
+SELECT * FROM pg_get_wal_record_content('FFFFFFFF/FFFFFFFF');
+
+SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_record_content(:'wal_lsn');
--
2.27.0