/*--------------------------------------------------------------------------
 *
 * test_custom_rmgrs.c
 *		Code for testing custom WAL resource managers.
 *
 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *		src/test/modules/test_custom_rmgrs/test_custom_rmgrs.c
 *
 * Custom WAL resource manager for records containing a simple textual
 * payload, no-op redo, and no decoding.
 *
 * -------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xloginsert.h"
#include "fmgr.h"
#include "utils/pg_lsn.h"
#include "varatt.h"

PG_MODULE_MAGIC;

/*
 * test_custom_rmgrs WAL record message.
 */
typedef struct xl_testcustomrmgrs_message
{
	Size		message_size;	/* size of the message */
	char		message[FLEXIBLE_ARRAY_MEMBER]; /* payload */
} xl_testcustomrmgrs_message;

#define SizeOfTestCustomRmgrsMessage	(offsetof(xl_testcustomrmgrs_message, message))
#define XLOG_TEST_CUSTOM_RMGRS_MESSAGE	0x00

/*
 * While developing or testing, use RM_EXPERIMENTAL_ID for rmid. For a real
 * extension, reserve a new resource manager ID to avoid conflicting with
 * other extensions; see:
 * https://wiki.postgresql.org/wiki/CustomWALResourceManagers
 */
#define RM_TESTCUSTOMRMGRS_ID			RM_EXPERIMENTAL_ID
#define TESTCUSTOMRMGRS_NAME			"test_custom_rmgrs"

/* RMGR API, see xlog_internal.h */
void		testcustomrmgrs_redo(XLogReaderState *record);
void		testcustomrmgrs_desc(StringInfo buf, XLogReaderState *record);
const char *testcustomrmgrs_identify(uint8 info);

static const RmgrData testcustomrmgrs_rmgr = {
	.rm_name = TESTCUSTOMRMGRS_NAME,
	.rm_redo = testcustomrmgrs_redo,
	.rm_desc = testcustomrmgrs_desc,
	.rm_identify = testcustomrmgrs_identify
};

/*
 * Module load callback
 */
void
_PG_init(void)
{
	/*
	 * In order to create our own custom resource manager, we have to be
	 * loaded via shared_preload_libraries. Otherwise, registration will fail.
	 */
	RegisterCustomRmgr(RM_TESTCUSTOMRMGRS_ID, &testcustomrmgrs_rmgr);
}

/* RMGR API implementation */

/*
 * Redo is just a noop for this module, because we aren't testing recovery of
 * any real structure.
 */
void
testcustomrmgrs_redo(XLogReaderState *record)
{
	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;

	if (info != XLOG_TEST_CUSTOM_RMGRS_MESSAGE)
		elog(PANIC, "testcustomrmgrs_redo: unknown op code %u", info);
}

void
testcustomrmgrs_desc(StringInfo buf, XLogReaderState *record)
{
	char	   *rec = XLogRecGetData(record);
	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;

	if (info == XLOG_TEST_CUSTOM_RMGRS_MESSAGE)
	{
		xl_testcustomrmgrs_message *xlrec = (xl_testcustomrmgrs_message *) rec;

		appendStringInfo(buf, "payload (%zu bytes): ", xlrec->message_size);
		appendBinaryStringInfo(buf, xlrec->message, xlrec->message_size);
	}
}

const char *
testcustomrmgrs_identify(uint8 info)
{
	if ((info & ~XLR_INFO_MASK) == XLOG_TEST_CUSTOM_RMGRS_MESSAGE)
		return "TEST_CUSTOM_RMGRS_MESSAGE";

	return NULL;
}

/*
 * SQL function for writing a simple message into WAL with the help of custom
 * WAL resource manager.
 */
PG_FUNCTION_INFO_V1(test_custom_rmgrs_insert_wal_record);
Datum
test_custom_rmgrs_insert_wal_record(PG_FUNCTION_ARGS)
{
	text	   *arg = PG_GETARG_TEXT_PP(0);
	char	   *payload = VARDATA_ANY(arg);
	Size		len = VARSIZE_ANY_EXHDR(arg);
	XLogRecPtr	lsn;
	xl_testcustomrmgrs_message xlrec;

	xlrec.message_size = len;

	XLogBeginInsert();
	XLogRegisterData((char *) &xlrec, SizeOfTestCustomRmgrsMessage);
	XLogRegisterData((char *) payload, len);

	/* Let's mark this record as unimportant, just in case. */
	XLogSetRecordFlags(XLOG_MARK_UNIMPORTANT);

	lsn = XLogInsert(RM_TESTCUSTOMRMGRS_ID, XLOG_TEST_CUSTOM_RMGRS_MESSAGE);

	PG_RETURN_LSN(lsn);
}
