From 0e97b5ca745238a9d23dbc964fc56b1f2d184f18 Mon Sep 17 00:00:00 2001 From: Hayato Kuroda Date: Fri, 4 Sep 2026 12:35:21 +0900 Subject: [PATCH v1 2/3] 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. --- src/backend/utils/misc/guc_parameters.dat | 2 +- src/bin/pg_basebackup/pg_createsubscriber.c | 39 ++++++++++++++++++- .../t/040_pg_createsubscriber.pl | 24 ++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 3c5e16ad1e7..6dc483b61d2 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 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..1ce31d5d739 100644 --- a/src/bin/pg_basebackup/pg_createsubscriber.c +++ b/src/bin/pg_basebackup/pg_createsubscriber.c @@ -1025,6 +1025,9 @@ check_publisher(const struct LogicalRepInfo *dbinfo) int cur_walsenders; int max_prepared_transactions; char *max_slot_wal_keep_size; + char *output_plugin_libraries; + char **allowed_plugins; + bool pgoutput_allowed = false; pg_log_info("checking settings on publisher"); @@ -1058,7 +1061,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) { @@ -1074,6 +1078,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); @@ -1086,6 +1091,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); @@ -1133,7 +1140,37 @@ check_publisher(const struct LogicalRepInfo *dbinfo) "max_slot_wal_keep_size"); } + if (!SplitGUCList(output_plugin_libraries, ',', &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(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 9252d1c3c5c..dc08d171edf 100644 --- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl +++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl @@ -334,6 +334,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. Use the same publication name for different # databases, since publication names are database-local. command_ok( -- 2.52.0