Add test module for Custom WAL Resource Manager feature.
authorJeff Davis <jdavis@postgresql.org>
Tue, 15 Nov 2022 21:10:27 +0000 (13:10 -0800)
committerJeff Davis <jdavis@postgresql.org>
Tue, 15 Nov 2022 23:26:14 +0000 (15:26 -0800)
Author: Bharath Rupireddy, Jeff Davis
Discussion: https://postgr.es/m/CALj2ACVTBNA1wfVCsikfhygAbZe6kFY8Oz6PhOyhHyA4vAGouA%40mail.gmail.com

doc/src/sgml/custom-rmgr.sgml
src/test/modules/Makefile
src/test/modules/meson.build
src/test/modules/test_custom_rmgrs/.gitignore [new file with mode: 0644]
src/test/modules/test_custom_rmgrs/Makefile [new file with mode: 0644]
src/test/modules/test_custom_rmgrs/meson.build [new file with mode: 0644]
src/test/modules/test_custom_rmgrs/t/001_basic.pl [new file with mode: 0644]
src/test/modules/test_custom_rmgrs/test_custom_rmgrs--1.0.sql [new file with mode: 0644]
src/test/modules/test_custom_rmgrs/test_custom_rmgrs.c [new file with mode: 0644]
src/test/modules/test_custom_rmgrs/test_custom_rmgrs.control [new file with mode: 0644]

index 2893016cef32641b8e3e7b7f946d7778b0159720..6d6909fc128502f92bcf6ea2d2b93883814cf689 100644 (file)
@@ -57,6 +57,13 @@ typedef struct RmgrData
 } RmgrData;
 </programlisting>
  </para>
+
+  <para>
+   The <filename>src/test/modules/test_custom_rmgrs</filename> module
+   contains a working example, which demonstrates usage of custom WAL
+   resource managers.
+  </para>
+
  <para>
   Then, register your new resource
   manager.
index 7b3f292965218fd0d42efa40366ff97b667f2ba2..548469f7c1d49a45e8f3dd1771ba93144de6de96 100644 (file)
@@ -16,6 +16,7 @@ SUBDIRS = \
          spgist_name_ops \
          test_bloomfilter \
          test_copy_callbacks \
+         test_custom_rmgrs \
          test_ddl_deparse \
          test_extensions \
          test_ginpostinglist \
index c2e5f5ffd5aab814d7fcf267de3bd8469b5bd4b4..f2df05b1bc340c0fae6e1a78c6228a25c08226d7 100644 (file)
@@ -10,6 +10,7 @@ subdir('spgist_name_ops')
 subdir('ssl_passphrase_callback')
 subdir('test_bloomfilter')
 subdir('test_copy_callbacks')
+subdir('test_custom_rmgrs')
 subdir('test_ddl_deparse')
 subdir('test_extensions')
 subdir('test_ginpostinglist')
diff --git a/src/test/modules/test_custom_rmgrs/.gitignore b/src/test/modules/test_custom_rmgrs/.gitignore
new file mode 100644 (file)
index 0000000..5dcb3ff
--- /dev/null
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_custom_rmgrs/Makefile b/src/test/modules/test_custom_rmgrs/Makefile
new file mode 100644 (file)
index 0000000..b557e58
--- /dev/null
@@ -0,0 +1,24 @@
+# src/test/modules/test_custom_rmgrs/Makefile
+
+MODULE_big = test_custom_rmgrs
+OBJS = \
+   $(WIN32RES) \
+   test_custom_rmgrs.o
+PGFILEDESC = "test_custom_rmgrs - test custom WAL resource managers"
+
+EXTENSION = test_custom_rmgrs
+DATA = test_custom_rmgrs--1.0.sql
+
+EXTRA_INSTALL = contrib/pg_walinspect
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_custom_rmgrs
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_custom_rmgrs/meson.build b/src/test/modules/test_custom_rmgrs/meson.build
new file mode 100644 (file)
index 0000000..05ec06d
--- /dev/null
@@ -0,0 +1,34 @@
+# FIXME: prevent install during main install, but not during test :/
+
+test_custom_rmgrs_sources = files(
+  'test_custom_rmgrs.c',
+)
+
+if host_system == 'windows'
+  test_custom_rmgrs_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_custom_rmgrs',
+    '--FILEDESC', 'test_custom_rmgrs - test custom WAL resource managers',])
+endif
+
+test_custom_rmgrs = shared_module('test_custom_rmgrs',
+  test_custom_rmgrs_sources,
+  kwargs: pg_mod_args,
+)
+testprep_targets += test_custom_rmgrs
+
+install_data(
+  'test_custom_rmgrs.control',
+  'test_custom_rmgrs--1.0.sql',
+  kwargs: contrib_data_args,
+)
+
+tests += {
+  'name': 'test_custom_rmgrs',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'tap': {
+    'tests': [
+      't/001_basic.pl',
+    ],
+  },
+}
diff --git a/src/test/modules/test_custom_rmgrs/t/001_basic.pl b/src/test/modules/test_custom_rmgrs/t/001_basic.pl
new file mode 100644 (file)
index 0000000..a5e3a88
--- /dev/null
@@ -0,0 +1,61 @@
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+
+$node->init;
+$node->append_conf(
+   'postgresql.conf', q{
+wal_level = 'replica'
+max_wal_senders = 4
+shared_preload_libraries = 'test_custom_rmgrs'
+});
+$node->start;
+
+# setup
+$node->safe_psql('postgres', 'CREATE EXTENSION test_custom_rmgrs');
+
+# pg_walinspect is required only for verifying test_custom_rmgrs output.
+# test_custom_rmgrs doesn't use/depend on it internally.
+$node->safe_psql('postgres', 'CREATE EXTENSION pg_walinspect');
+
+# make sure checkpoints don't interfere with the test.
+my $start_lsn = $node->safe_psql('postgres',
+   qq[SELECT lsn FROM pg_create_physical_replication_slot('regress_test_slot1', true, false);]);
+
+# write and save the WAL record's returned end LSN for verifying it later
+my $record_end_lsn = $node->safe_psql('postgres',
+   'SELECT * FROM test_custom_rmgrs_insert_wal_record(\'payload123\')');
+
+# ensure the WAL is written and flushed to disk
+$node->safe_psql('postgres', 'SELECT pg_switch_wal()');
+
+my $end_lsn = $node->safe_psql('postgres', 'SELECT pg_current_wal_flush_lsn()');
+
+# check if our custom WAL resource manager has successfully registered with the server
+my $row_count =
+  $node->safe_psql('postgres',
+   qq[SELECT count(*) FROM pg_get_wal_resource_managers()
+       WHERE rm_name = 'test_custom_rmgrs';]);
+is($row_count, '1',
+   'custom WAL resource manager has successfully registered with the server'
+);
+
+# check if our custom WAL resource manager has successfully written a WAL record
+my $expected = qq($record_end_lsn|test_custom_rmgrs|TEST_CUSTOM_RMGRS_MESSAGE|44|18|0|payload (10 bytes): payload123);
+my $result =
+  $node->safe_psql('postgres',
+   qq[SELECT end_lsn, resource_manager, record_type, record_length, main_data_length, fpi_length, description FROM pg_get_wal_records_info('$start_lsn', '$end_lsn')
+       WHERE resource_manager = 'test_custom_rmgrs';]);
+is($result, $expected,
+   'custom WAL resource manager has successfully written a WAL record'
+);
+
+$node->stop;
+done_testing();
diff --git a/src/test/modules/test_custom_rmgrs/test_custom_rmgrs--1.0.sql b/src/test/modules/test_custom_rmgrs/test_custom_rmgrs--1.0.sql
new file mode 100644 (file)
index 0000000..ea2c444
--- /dev/null
@@ -0,0 +1,16 @@
+/* src/test/modules/test_custom_rmgrs/test_custom_rmgrs--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_custom_rmgrs" to load this file. \quit
+
+--
+-- test_custom_rmgrs_insert_wal_record()
+--
+-- Writes a simple message into WAL with the help of custom WAL
+-- resource manager.
+--
+CREATE FUNCTION test_custom_rmgrs_insert_wal_record(IN payload TEXT,
+    OUT lsn pg_lsn
+)
+AS 'MODULE_PATHNAME', 'test_custom_rmgrs_insert_wal_record'
+LANGUAGE C STRICT PARALLEL UNSAFE;
diff --git a/src/test/modules/test_custom_rmgrs/test_custom_rmgrs.c b/src/test/modules/test_custom_rmgrs/test_custom_rmgrs.c
new file mode 100644 (file)
index 0000000..6e5270b
--- /dev/null
@@ -0,0 +1,139 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_custom_rmgrs.c
+ *     Code for testing custom WAL resource managers.
+ *
+ * Portions Copyright (c) 1996-2022, 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"
+
+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 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);
+}
diff --git a/src/test/modules/test_custom_rmgrs/test_custom_rmgrs.control b/src/test/modules/test_custom_rmgrs/test_custom_rmgrs.control
new file mode 100644 (file)
index 0000000..8113e00
--- /dev/null
@@ -0,0 +1,4 @@
+comment = 'Test code for custom WAL resource managers'
+default_version = '1.0'
+module_pathname = '$libdir/test_custom_rmgrs'
+relocatable = true