From e1cbde65108376c61377f12089bdc3990b53330e Mon Sep 17 00:00:00 2001 From: Hayato Kuroda 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