From 8e154fa5a3a8f56ab21a2bdd1848770569f9aa93 Mon Sep 17 00:00:00 2001
From: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Date: Fri, 4 Sep 2026 12:35:21 +0900
Subject: [PATCH v2-PG18 1/2] pg_createsubscriber: ensure
 output_plugin_libraries include "pgoutput"

pg_createsubscriber always uses pgoutput when creating logical replication
slots.  Check that pgoutput is included in the publisher's
output_plugin_libraries setting before proceeding, and provide a useful error
and hint when it is not.

This also allows --dry-run to detect the invalid configuration before any
replication slots are created.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     |  3 ++
 src/bin/pg_basebackup/pg_createsubscriber.c   | 44 ++++++++++++++++++-
 .../t/040_pg_createsubscriber.pl              | 24 ++++++++++
 3 files changed, 70 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index bb9cc72576c..427df49b692 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -385,6 +385,9 @@ PostgreSQL documentation
     replication slots.  The source server must have <xref
     linkend="guc-max-wal-senders"/> configured to a value greater than or equal
     to the number of specified databases and existing WAL sender processes.
+    The source server must also allow the <literal>pgoutput</literal> output
+    plugin by setting <xref linkend="guc-output-plugin-libraries"/> to include
+    <literal>pgoutput</literal>.
    </para>
   </refsect2>
 
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index 42a073e921f..8de72ed4247 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -886,6 +886,10 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 	int			cur_walsenders;
 	int			max_prepared_transactions;
 	char	   *max_slot_wal_keep_size;
+	char	   *output_plugin_libraries;
+	char	   *output_plugin_libraries_copy;
+	char	  **allowed_plugins;
+	bool		pgoutput_allowed = false;
 
 	pg_log_info("checking settings on publisher");
 
@@ -910,6 +914,7 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 	 * - max_replication_slots >= current + number of dbs to be converted
 	 * - max_wal_senders >= current + number of dbs to be converted
 	 * - max_slot_wal_keep_size = -1 (to prevent deletion of required WAL files)
+	 * - output_plugin_libraries must include 'pgoutput'
 	 * -----------------------------------------------------------------------
 	 */
 	res = PQexec(conn,
@@ -919,7 +924,8 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 				 " pg_catalog.current_setting('max_wal_senders'),"
 				 " (SELECT count(*) FROM pg_catalog.pg_stat_activity WHERE backend_type = 'walsender'),"
 				 " pg_catalog.current_setting('max_prepared_transactions'),"
-				 " pg_catalog.current_setting('max_slot_wal_keep_size')");
+				 " pg_catalog.current_setting('max_slot_wal_keep_size'),"
+				 " pg_catalog.current_setting('output_plugin_libraries')");
 
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 	{
@@ -935,6 +941,7 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 	cur_walsenders = atoi(PQgetvalue(res, 0, 4));
 	max_prepared_transactions = atoi(PQgetvalue(res, 0, 5));
 	max_slot_wal_keep_size = pg_strdup(PQgetvalue(res, 0, 6));
+	output_plugin_libraries = pg_strdup(PQgetvalue(res, 0, 7));
 
 	PQclear(res);
 
@@ -947,6 +954,8 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 				 max_prepared_transactions);
 	pg_log_debug("publisher: max_slot_wal_keep_size: %s",
 				 max_slot_wal_keep_size);
+	pg_log_debug("publisher: output_plugin_libraries: %s",
+				 output_plugin_libraries);
 
 	disconnect_database(conn, false);
 
@@ -994,7 +1003,40 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 							"max_slot_wal_keep_size");
 	}
 
+	output_plugin_libraries_copy = pg_strdup(output_plugin_libraries);
+
+	if (!SplitGUCList(output_plugin_libraries_copy, ',', &allowed_plugins))
+	{
+		/*
+		 * Should not happen. (Frontend and backend GUC_LIST_QUOTE parsing
+		 * have to remain compatible for pg_dump at minimum.)
+		 */
+		pg_fatal("could not parse \"output_plugin_libraries\" setting '%s'",
+				 output_plugin_libraries);
+	}
+
+	/* Make sure the output_plugin_libraries setting includes "pgoutput" */
+	for (char **plugin = allowed_plugins; *plugin; plugin++)
+	{
+		if (strcmp(*plugin, "pgoutput") == 0)
+		{
+			pgoutput_allowed = true;
+			break;
+		}
+	}
+
+	if (!pgoutput_allowed)
+	{
+		pg_log_error("publisher does not allow the \"pgoutput\" output plugin");
+		pg_log_error_hint("Add \"pgoutput\" to the configuration parameter \"%s\" and reload the server configuration.",
+						  "output_plugin_libraries");
+		failed = true;
+	}
+
 	pg_free(wal_level);
+	pg_free(output_plugin_libraries);
+	pg_free(output_plugin_libraries_copy);
+	pg_free(allowed_plugins);
 
 	if (failed)
 		exit(1);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 09014be1361..ab564b93d3b 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -346,6 +346,30 @@ is($node_s->safe_psql($db1, "SELECT COUNT(*) FROM pg_publication"),
 
 $node_s->stop;
 
+# pg_createsubscriber requires that output_plugin_libraries includes 'pgoutput'
+# on the publisher.
+$node_p->append_conf('postgresql.conf',
+	"output_plugin_libraries = 'test_decoding'");
+$node_p->reload;
+
+command_fails_like(
+	[
+		'pg_createsubscriber',
+		'--verbose',
+		'--dry-run',
+		'--pgdata' => $node_s->data_dir,
+		'--publisher-server' => $node_p->connstr($db1),
+		'--socketdir' => $node_s->host,
+		'--subscriber-port' => $node_s->port,
+		'--database' => $db1,
+	],
+	qr/publisher does not allow the "pgoutput" output plugin/,
+	'primary does not allow to load pgoutput plugin');
+
+$node_p->append_conf('postgresql.conf',
+	"output_plugin_libraries = 'pgoutput, test_decoding'");
+$node_p->reload;
+
 # dry run mode on node S
 command_ok(
 	[
-- 
2.52.0

