/*-------------------------------------------------------------------------
 *
 * pg_walinspect.c
 *		  Functions to inspect contents of PostgreSQL Write-Ahead Log
 *
 * Copyright (c) 2022-2024, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		  contrib/pg_walinspect/pg_walinspect.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogreader.h"
#include "access/xlogrecovery.h"
#include "access/xlogstats.h"
#include "access/xlogutils.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/pg_lsn.h"

/*
 * NOTE: For any code change or issue fix here, it is highly recommended to
 * give a thought about doing the same in pg_waldump tool as well.
 */

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(pg_get_wal_block_info);
PG_FUNCTION_INFO_V1(pg_get_wal_record_info);
PG_FUNCTION_INFO_V1(pg_get_wal_records_info);
PG_FUNCTION_INFO_V1(pg_get_wal_records_info_till_end_of_wal);
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);
static void GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
								Datum *values, bool *nulls, uint32 ncols,
								bool stats_per_record);
static void FillXLogStatsRow(const char *name, uint64 n, uint64 total_count,
							 uint64 rec_len, uint64 total_rec_len,
							 uint64 fpi_len, uint64 total_fpi_len,
							 uint64 tot_len, uint64 total_len,
							 Datum *values, bool *nulls, uint32 ncols);
static void GetWalStats(FunctionCallInfo fcinfo,
						XLogRecPtr start_lsn,
						XLogRecPtr end_lsn,
						bool stats_per_record);
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
 * records that have block references.)
 *
 * This function leaks memory.  Caller may need to use its own custom memory
 * context.
 *
 * Keep this in sync with GetWALRecordInfo.
 */
static void
GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record,
				bool show_data)
{
#define PG_GET_WAL_BLOCK_INFO_COLS 20
	int			block_id;
	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	RmgrData	desc;
	const char *record_type;
	StringInfoData rec_desc;

	Assert(XLogRecHasAnyBlockRefs(record));

	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);

	for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
	{
		DecodedBkpBlock *blk;
		BlockNumber blkno;
		RelFileLocator rnode;
		ForkNumber	forknum;
		Datum		values[PG_GET_WAL_BLOCK_INFO_COLS] = {0};
		bool		nulls[PG_GET_WAL_BLOCK_INFO_COLS] = {0};
		uint32		block_data_len = 0,
					block_fpi_len = 0;
		ArrayType  *block_fpi_info = NULL;
		int			i = 0;

		if (!XLogRecHasBlockRef(record, block_id))
			continue;

		blk = XLogRecGetBlock(record, block_id);

		(void) XLogRecGetBlockTagExtended(record, block_id,
										  &rnode, &forknum, &blkno, NULL);

		/* Save block_data_len */
		if (blk->has_data)
			block_data_len = blk->data_len;

		if (blk->has_image)
		{
			/* Block reference has an FPI, so prepare relevant output */
			int			bitcnt;
			int			cnt = 0;
			Datum	   *flags;

			/* Save block_fpi_len */
			block_fpi_len = blk->bimg_len;

			/* Construct and save block_fpi_info */
			bitcnt = pg_popcount((const char *) &blk->bimg_info,
								 sizeof(uint8));
			flags = (Datum *) palloc0(sizeof(Datum) * bitcnt);
			if ((blk->bimg_info & BKPIMAGE_HAS_HOLE) != 0)
				flags[cnt++] = CStringGetTextDatum("HAS_HOLE");
			if (blk->apply_image)
				flags[cnt++] = CStringGetTextDatum("APPLY");
			if ((blk->bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0)
				flags[cnt++] = CStringGetTextDatum("COMPRESS_PGLZ");
			if ((blk->bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0)
				flags[cnt++] = CStringGetTextDatum("COMPRESS_LZ4");
			if ((blk->bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0)
				flags[cnt++] = CStringGetTextDatum("COMPRESS_ZSTD");

			Assert(cnt <= bitcnt);
			block_fpi_info = construct_array_builtin(flags, cnt, TEXTOID);
		}

		/* start_lsn, end_lsn, prev_lsn, and blockid outputs */
		values[i++] = LSNGetDatum(record->ReadRecPtr);
		values[i++] = LSNGetDatum(record->EndRecPtr);
		values[i++] = LSNGetDatum(XLogRecGetPrev(record));
		values[i++] = Int16GetDatum(block_id);

		/* relfile and block related outputs */
		values[i++] = ObjectIdGetDatum(blk->rlocator.spcOid);
		values[i++] = ObjectIdGetDatum(blk->rlocator.dbOid);
		values[i++] = ObjectIdGetDatum(blk->rlocator.relNumber);
		values[i++] = Int16GetDatum(forknum);
		values[i++] = Int64GetDatum((int64) blkno);

		/* xid, resource_manager, and record_type outputs */
		values[i++] = TransactionIdGetDatum(XLogRecGetXid(record));
		values[i++] = CStringGetTextDatum(desc.rm_name);
		values[i++] = CStringGetTextDatum(record_type);

		/*
		 * record_length, main_data_length, block_data_len, and
		 * block_fpi_length outputs
		 */
		values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record));
		values[i++] = UInt32GetDatum(XLogRecGetDataLen(record));
		values[i++] = UInt32GetDatum(block_data_len);
		values[i++] = UInt32GetDatum(block_fpi_len);

		/* block_fpi_info (text array) output */
		if (block_fpi_info)
			values[i++] = PointerGetDatum(block_fpi_info);
		else
			nulls[i++] = true;

		/* description output (describes WAL record) */
		if (rec_desc.len > 0)
			values[i++] = CStringGetTextDatum(rec_desc.data);
		else
			nulls[i++] = true;

		/* block_data output */
		if (blk->has_data && show_data)
		{
			bytea	   *block_data;

			block_data = (bytea *) palloc(block_data_len + VARHDRSZ);
			SET_VARSIZE(block_data, block_data_len + VARHDRSZ);
			memcpy(VARDATA(block_data), blk->data, block_data_len);
			values[i++] = PointerGetDatum(block_data);
		}
		else
			nulls[i++] = true;

		/* block_fpi_data output */
		if (blk->has_image && show_data)
		{
			PGAlignedBlock buf;
			Page		page;
			bytea	   *block_fpi_data;

			page = (Page) buf.data;
			if (!RestoreBlockImage(record, block_id, page))
				ereport(ERROR,
						(errcode(ERRCODE_INTERNAL_ERROR),
						 errmsg_internal("%s", record->errormsg_buf)));

			block_fpi_data = (bytea *) palloc(BLCKSZ + VARHDRSZ);
			SET_VARSIZE(block_fpi_data, BLCKSZ + VARHDRSZ);
			memcpy(VARDATA(block_fpi_data), page, BLCKSZ);
			values[i++] = PointerGetDatum(block_fpi_data);
		}
		else
			nulls[i++] = true;

		Assert(i == PG_GET_WAL_BLOCK_INFO_COLS);

		/* Store a tuple for this block reference */
		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
							 values, nulls);
	}

#undef PG_GET_WAL_BLOCK_INFO_COLS
}

/*
 * Get WAL record info, unnested by block reference
 */
Datum
pg_get_wal_block_info(PG_FUNCTION_ARGS)
{
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
	XLogRecPtr	end_lsn = PG_GETARG_LSN(1);
	bool		show_data = PG_GETARG_BOOL(2);
	XLogReaderState *xlogreader;
	MemoryContext old_cxt;
	MemoryContext tmp_cxt;

	ValidateInputLSNs(start_lsn, &end_lsn);

	InitMaterializedSRF(fcinfo, 0);

	xlogreader = InitXLogReaderState(start_lsn);

	tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
									"pg_get_wal_block_info temporary cxt",
									ALLOCSET_DEFAULT_SIZES);

	while (ReadNextXLogRecord(xlogreader) &&
		   xlogreader->EndRecPtr <= end_lsn)
	{
		CHECK_FOR_INTERRUPTS();

		if (!XLogRecHasAnyBlockRefs(xlogreader))
			continue;

		/* Use the tmp context so we can clean up after each tuple is done */
		old_cxt = MemoryContextSwitchTo(tmp_cxt);

		GetWALBlockInfo(fcinfo, xlogreader, show_data);

		/* clean up and switch back */
		MemoryContextSwitchTo(old_cxt);
		MemoryContextReset(tmp_cxt);
	}

	MemoryContextDelete(tmp_cxt);
	pfree(xlogreader->private_data);
	XLogReaderFree(xlogreader);

	PG_RETURN_VOID();
}

/*
 * Get WAL record info.
 */
Datum
pg_get_wal_record_info(PG_FUNCTION_ARGS)
{
#define PG_GET_WAL_RECORD_INFO_COLS 11
	Datum		result;
	Datum		values[PG_GET_WAL_RECORD_INFO_COLS] = {0};
	bool		nulls[PG_GET_WAL_RECORD_INFO_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_INFO_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_INFO_COLS
}

/*
 * Validate start and end LSNs coming from the function inputs.
 *
 * If end_lsn is found to be higher than the current LSN reported by the
 * cluster, use the current LSN as the upper bound.
 */
static void
ValidateInputLSNs(XLogRecPtr start_lsn, XLogRecPtr *end_lsn)
{
	XLogRecPtr	curr_lsn = GetCurrentLSN();

	if (start_lsn > curr_lsn)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("WAL start LSN must be less than current LSN"),
				 errdetail("Current WAL LSN on the database system is at %X/%X.",
						   LSN_FORMAT_ARGS(curr_lsn))));

	if (start_lsn > *end_lsn)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("WAL start LSN must be less than end LSN")));

	if (*end_lsn > curr_lsn)
		*end_lsn = curr_lsn;
}

/*
 * Get info of all WAL records between start LSN and end LSN.
 */
static void
GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr start_lsn,
				  XLogRecPtr end_lsn)
{
#define PG_GET_WAL_RECORDS_INFO_COLS 11
	XLogReaderState *xlogreader;
	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	MemoryContext old_cxt;
	MemoryContext tmp_cxt;

	Assert(start_lsn <= end_lsn);

	InitMaterializedSRF(fcinfo, 0);

	xlogreader = InitXLogReaderState(start_lsn);

	tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
									"GetWALRecordsInfo temporary cxt",
									ALLOCSET_DEFAULT_SIZES);

	while (ReadNextXLogRecord(xlogreader) &&
		   xlogreader->EndRecPtr <= end_lsn)
	{
		Datum		values[PG_GET_WAL_RECORDS_INFO_COLS] = {0};
		bool		nulls[PG_GET_WAL_RECORDS_INFO_COLS] = {0};

		/* Use the tmp context so we can clean up after each tuple is done */
		old_cxt = MemoryContextSwitchTo(tmp_cxt);

		GetWALRecordInfo(xlogreader, values, nulls,
						 PG_GET_WAL_RECORDS_INFO_COLS);

		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
							 values, nulls);

		/* clean up and switch back */
		MemoryContextSwitchTo(old_cxt);
		MemoryContextReset(tmp_cxt);

		CHECK_FOR_INTERRUPTS();
	}

	MemoryContextDelete(tmp_cxt);
	pfree(xlogreader->private_data);
	XLogReaderFree(xlogreader);

#undef PG_GET_WAL_RECORDS_INFO_COLS
}

/*
 * Get info of all WAL records between start LSN and end LSN.
 */
Datum
pg_get_wal_records_info(PG_FUNCTION_ARGS)
{
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
	XLogRecPtr	end_lsn = PG_GETARG_LSN(1);

	ValidateInputLSNs(start_lsn, &end_lsn);
	GetWALRecordsInfo(fcinfo, start_lsn, end_lsn);

	PG_RETURN_VOID();
}

/*
 * Fill single row of record counts and sizes for an rmgr or record.
 */
static void
FillXLogStatsRow(const char *name,
				 uint64 n, uint64 total_count,
				 uint64 rec_len, uint64 total_rec_len,
				 uint64 fpi_len, uint64 total_fpi_len,
				 uint64 tot_len, uint64 total_len,
				 Datum *values, bool *nulls, uint32 ncols)
{
	double		n_pct,
				rec_len_pct,
				fpi_len_pct,
				tot_len_pct;
	int			i = 0;

	n_pct = 0;
	if (total_count != 0)
		n_pct = 100 * (double) n / total_count;

	rec_len_pct = 0;
	if (total_rec_len != 0)
		rec_len_pct = 100 * (double) rec_len / total_rec_len;

	fpi_len_pct = 0;
	if (total_fpi_len != 0)
		fpi_len_pct = 100 * (double) fpi_len / total_fpi_len;

	tot_len_pct = 0;
	if (total_len != 0)
		tot_len_pct = 100 * (double) tot_len / total_len;

	values[i++] = CStringGetTextDatum(name);
	values[i++] = Int64GetDatum(n);
	values[i++] = Float8GetDatum(n_pct);
	values[i++] = Int64GetDatum(rec_len);
	values[i++] = Float8GetDatum(rec_len_pct);
	values[i++] = Int64GetDatum(fpi_len);
	values[i++] = Float8GetDatum(fpi_len_pct);
	values[i++] = Int64GetDatum(tot_len);
	values[i++] = Float8GetDatum(tot_len_pct);

	Assert(i == ncols);
}

/*
 * Get summary statistics about the records seen so far.
 */
static void
GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo,
					Datum *values, bool *nulls, uint32 ncols,
					bool stats_per_record)
{
	MemoryContext old_cxt;
	MemoryContext tmp_cxt;
	uint64		total_count = 0;
	uint64		total_rec_len = 0;
	uint64		total_fpi_len = 0;
	uint64		total_len = 0;
	int			ri;

	/*
	 * Each row shows its percentages of the total, so make a first pass to
	 * calculate column totals.
	 */
	for (ri = 0; ri <= RM_MAX_ID; ri++)
	{
		if (!RmgrIdIsValid(ri))
			continue;

		total_count += stats->rmgr_stats[ri].count;
		total_rec_len += stats->rmgr_stats[ri].rec_len;
		total_fpi_len += stats->rmgr_stats[ri].fpi_len;
	}
	total_len = total_rec_len + total_fpi_len;

	tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
									"GetXLogSummaryStats temporary cxt",
									ALLOCSET_DEFAULT_SIZES);

	for (ri = 0; ri <= RM_MAX_ID; ri++)
	{
		uint64		count;
		uint64		rec_len;
		uint64		fpi_len;
		uint64		tot_len;
		RmgrData	desc;

		if (!RmgrIdIsValid(ri))
			continue;

		if (!RmgrIdExists(ri))
			continue;

		desc = GetRmgr(ri);

		if (stats_per_record)
		{
			int			rj;

			for (rj = 0; rj < MAX_XLINFO_TYPES; rj++)
			{
				const char *id;

				count = stats->record_stats[ri][rj].count;
				rec_len = stats->record_stats[ri][rj].rec_len;
				fpi_len = stats->record_stats[ri][rj].fpi_len;
				tot_len = rec_len + fpi_len;

				/* Skip undefined combinations and ones that didn't occur */
				if (count == 0)
					continue;

				old_cxt = MemoryContextSwitchTo(tmp_cxt);

				/* the upper four bits in xl_info are the rmgr's */
				id = desc.rm_identify(rj << 4);
				if (id == NULL)
					id = psprintf("UNKNOWN (%x)", rj << 4);

				FillXLogStatsRow(psprintf("%s/%s", desc.rm_name, id), count,
								 total_count, rec_len, total_rec_len, fpi_len,
								 total_fpi_len, tot_len, total_len,
								 values, nulls, ncols);

				tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
									 values, nulls);

				/* clean up and switch back */
				MemoryContextSwitchTo(old_cxt);
				MemoryContextReset(tmp_cxt);
			}
		}
		else
		{
			count = stats->rmgr_stats[ri].count;
			rec_len = stats->rmgr_stats[ri].rec_len;
			fpi_len = stats->rmgr_stats[ri].fpi_len;
			tot_len = rec_len + fpi_len;

			old_cxt = MemoryContextSwitchTo(tmp_cxt);

			FillXLogStatsRow(desc.rm_name, count, total_count, rec_len,
							 total_rec_len, fpi_len, total_fpi_len, tot_len,
							 total_len, values, nulls, ncols);

			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
								 values, nulls);

			/* clean up and switch back */
			MemoryContextSwitchTo(old_cxt);
			MemoryContextReset(tmp_cxt);
		}
	}

	MemoryContextDelete(tmp_cxt);
}

/*
 * Get WAL stats between start LSN and end LSN.
 */
static void
GetWalStats(FunctionCallInfo fcinfo, XLogRecPtr start_lsn, XLogRecPtr end_lsn,
			bool stats_per_record)
{
#define PG_GET_WAL_STATS_COLS 9
	XLogReaderState *xlogreader;
	XLogStats	stats = {0};
	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	Datum		values[PG_GET_WAL_STATS_COLS] = {0};
	bool		nulls[PG_GET_WAL_STATS_COLS] = {0};

	Assert(start_lsn <= end_lsn);

	InitMaterializedSRF(fcinfo, 0);

	xlogreader = InitXLogReaderState(start_lsn);

	while (ReadNextXLogRecord(xlogreader) &&
		   xlogreader->EndRecPtr <= end_lsn)
	{
		XLogRecStoreStats(&stats, xlogreader);

		CHECK_FOR_INTERRUPTS();
	}

	pfree(xlogreader->private_data);
	XLogReaderFree(xlogreader);

	GetXLogSummaryStats(&stats, rsinfo, values, nulls,
						PG_GET_WAL_STATS_COLS,
						stats_per_record);

#undef PG_GET_WAL_STATS_COLS
}

/*
 * Get stats of all WAL records between start LSN and end LSN.
 */
Datum
pg_get_wal_stats(PG_FUNCTION_ARGS)
{
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
	XLogRecPtr	end_lsn = PG_GETARG_LSN(1);
	bool		stats_per_record = PG_GETARG_BOOL(2);

	ValidateInputLSNs(start_lsn, &end_lsn);
	GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record);

	PG_RETURN_VOID();
}

/*
 * The following functions have been removed in newer versions in 1.1, but
 * they are kept around for compatibility.
 */
Datum
pg_get_wal_records_info_till_end_of_wal(PG_FUNCTION_ARGS)
{
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
	XLogRecPtr	end_lsn = GetCurrentLSN();

	if (start_lsn > end_lsn)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("WAL start LSN must be less than current LSN"),
				 errdetail("Current WAL LSN on the database system is at %X/%X.",
						   LSN_FORMAT_ARGS(end_lsn))));

	GetWALRecordsInfo(fcinfo, start_lsn, end_lsn);

	PG_RETURN_VOID();
}

Datum
pg_get_wal_stats_till_end_of_wal(PG_FUNCTION_ARGS)
{
	XLogRecPtr	start_lsn = PG_GETARG_LSN(0);
	XLogRecPtr	end_lsn = GetCurrentLSN();
	bool		stats_per_record = PG_GETARG_BOOL(1);

	if (start_lsn > end_lsn)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("WAL start LSN must be less than current LSN"),
				 errdetail("Current WAL LSN on the database system is at %X/%X.",
						   LSN_FORMAT_ARGS(end_lsn))));

	GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record);

	PG_RETURN_VOID();
}
