From 09d751a5cd990d38c1f5eae9b20c4ff47f918321 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 v3 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/backend/utils/misc/guc_parameters.dat     |  2 +-
 src/bin/pg_basebackup/pg_createsubscriber.c   | 76 +++++++++++++++++++
 .../t/040_pg_createsubscriber.pl              | 22 ++++++
 4 files changed, 102 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index 193f60628bc..03f49991faf 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -431,6 +431,9 @@ PostgreSQL documentation
     databases plus existing 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/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3c5e16ad1e7..6f68753a6b6 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2328,7 +2328,7 @@
 { name => 'output_plugin_libraries', type => 'string', context => 'PGC_SUSET', group => 'REPLICATION_SENDING',
   short_desc => 'Lists libraries that may be named as logical decoding output plugins.',
   long_desc => 'Users with REPLICATION privileges may only use plugins in this list when creating logical replication slots.',
-  # Note that src/bin/pg_upgrade/check.c assumes GUC_LIST_QUOTE here.
+  # Note that src/bin/pg_upgrade/check.c and src/bin/pg_basebackup/pg_createsubscriber.c assume GUC_LIST_QUOTE here.
   flags => 'GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_SUPERUSER_ONLY',
   variable => 'output_plugin_libraries_string',
   boot_val => '"pgoutput, test_decoding"',
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index 20b354aed56..7423d8cc681 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -1006,6 +1006,79 @@ make_output_dirs(const char *log_basedir)
 		pg_fatal("could not create directory \"%s\": %m", logdir);
 }
 
+/*
+ * Check per-database settings on the publisher
+ */
+static void
+check_publisher_per_database(const struct LogicalRepInfo *dbinfo)
+{
+	for (int i = 0; i < num_dbs; i++)
+	{
+		PGconn	   *conn;
+		PGresult   *res;
+		char	   *output_plugin_libraries;
+		char	   *output_plugin_libraries_copy;
+		char	  **allowed_plugins;
+		bool		pgoutput_allowed = false;
+
+		conn = connect_database(dbinfo[i].pubconninfo, true);
+
+		/* Check whether output_plugin_libraries includes 'pgoutput' */
+		res = PQexec(conn,
+					 "SELECT pg_catalog.current_setting('output_plugin_libraries')");
+
+		if (PQresultStatus(res) != PGRES_TUPLES_OK)
+		{
+			pg_log_error("could not obtain publisher settings in database \"%s\": %s",
+						 dbinfo[i].dbname, PQresultErrorMessage(res));
+			disconnect_database(conn, true);
+		}
+
+		output_plugin_libraries = pg_strdup(PQgetvalue(res, 0, 0));
+
+		PQclear(res);
+		pg_log_debug("publisher in database \"%s\": output_plugin_libraries: %s",
+					 dbinfo[i].dbname, output_plugin_libraries);
+
+		disconnect_database(conn, false);
+
+		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' in database \"%s\"",
+					 output_plugin_libraries, dbinfo[i].dbname);
+		}
+
+		/* 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 in database \"%s\"",
+						 dbinfo[i].dbname);
+			pg_log_error_hint("Add \"pgoutput\" to the configuration parameter \"%s\".",
+							  "output_plugin_libraries");
+			exit(1);
+		}
+
+		pg_free(output_plugin_libraries);
+		pg_free(output_plugin_libraries_copy);
+		pg_free(allowed_plugins);
+	}
+}
+
 /*
  * Is the primary server ready for logical replication?
  *
@@ -1137,6 +1210,9 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 
 	if (failed)
 		exit(1);
+
+	/* Also check per-database settings on the publisher */
+	check_publisher_per_database(dbinfo);
 }
 
 /*
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 9252d1c3c5c..795eb9ef350 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -334,6 +334,28 @@ 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->safe_psql($db2,
+	"ALTER DATABASE \"$db2\" SET output_plugin_libraries = 'test_decoding'");
+
+command_fails_like(
+	[
+		'pg_createsubscriber',
+		'--dry-run',
+		'--pgdata' => $node_s->data_dir,
+		'--publisher-server' => $node_p->connstr($db1),
+		'--socketdir' => $node_s->host,
+		'--subscriber-port' => $node_s->port,
+		'--database' => $db1,
+		'--database' => $db2,
+	],
+	qr/publisher does not allow the "pgoutput" output plugin in database "\Q$db2\E"/,
+	'primary does not allow to load pgoutput plugin');
+
+$node_p->safe_psql($db2,
+	"ALTER DATABASE \"$db2\" RESET output_plugin_libraries");
+
 # dry run mode on node S. Use the same publication name for different
 # databases, since publication names are database-local.
 command_ok(
-- 
2.52.0

