Thread: ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function
In the current code (PGSQL ver 12), we use a function with SECURITY DEFINER for refreshing subscriptions:
---
DECLARE
BEGIN
execute 'alter subscription ' || sSubName || 'REFRESH PUBLICATION';
raise notice 'Subscription % refreshed', sSubName;
END
---
The function is called during a deployment of a new version of our application when a deploy service account (NON-SUPERUSER) ENABLE and REFRESH subscriptions at the end of the deploy.
ERROR: ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function
I found only one discussion about this problem, where as a workaround is suggested using of dblink (https://postgrespro.com/list/id/CANaTPsphRF+7k+YANMv8goGu3oQLY9XtACpkec8Ju=mr59GHGA@mail.gmail.com#head)
Is there any other possibility or a recommendation to solve this case?
ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function in all PGSQL versions 14+?
Thanks in advance for your help.
Roman Sindelar
we prepared a test case to demonstrate our case.
TASK
---
ERROR: ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function
CONTEXT: SQL statement "alter subscription test_sub REFRESH PUBLICATION"
PL/pgSQL function test.refresh_subscription(character varying) line 4 at EXECUTE
---
QUESTIONS
- ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function in all PGSQL versions 14+, is it desired behavior?
- Is there any other possibility or a recommendation to solve our case/task?
--
-- ALTER SUBSCRIPTION REFRESH TEST CASE
--
-- DATABASES
-- db1 - source db
-- db2 - destination db (replica)
--
-- USERS
-- postgres - superuser
-- usr_db_repl - user for replication
-- usr_db_deploy - deploy user
--
-- PG_HBA.CONF
-- host replication usr_db_repl localhost trust
-- host db1, db2 usr_db_repl localhost trust
-- host db1, db2 usr_db_deploy localhost trust
-- local db1, db2 usr_db_deploy trust
--
--
-- PREPARATION TEST ENVIRONMENT
--
-- create users
create user usr_db_repl replication;
create user usr_db_deploy;
-- create databases
create database db1;
grant create on database db1 to usr_db_deploy;
create database db2;
grant create on database db2 to usr_db_deploy;
-- create source table
\c db1 usr_db_deploy
create schema test;
create table test.tab1 (id int primary key, num int);
grant usage on schema test to usr_db_repl;
grant select on table test.tab1 to usr_db_repl;
insert into test.tab1 values (1, 10);
select * from test.tab1;
-- create publication on source side
create publication test_pub for table test.tab1;
select * from pg_publication_tables;
-- create replication slot on source side
\c db1 postgres
select pg_create_logical_replication_slot('test_sub', 'pgoutput');
select * from pg_replication_slots;
-- create table on destination side
\c db2 usr_db_deploy
create schema test;
grant usage on schema test to usr_db_repl;
create table test.tab1 (id int primary key, num int);
-- create subscription on destination side using pre-prepared replication slot
\c db2 postgres
create subscription test_sub connection 'host=localhost port=5432 dbname=db1 user=usr_db_repl' publication test_pub with (slot_name=test_sub, create_slot=false);
select * from pg_subscription;
-- check replicated table
\c db2 usr_db_deploy
select * from test.tab1;
-- create functions with security definer on destination side
\c db2 postgres
create procedure test.disable_subscription(in ssubname character varying)
language plpgsql
security definer
as $procedure$
declare
begin
execute 'alter subscription ' || sSubName || ' disable';
raise notice 'Subscription % refreshed', sSubName;
end $procedure$
;
create procedure test.enable_subscription(in ssubname character varying)
language plpgsql
security definer
as $procedure$
declare
begin
execute 'alter subscription ' || sSubName || ' enable';
raise notice 'Subscription % refreshed', sSubName;
end $procedure$
;
create procedure test.refresh_subscription(in ssubname character varying)
language plpgsql
security definer
as $procedure$
declare
begin
execute 'alter subscription ' || sSubName || ' REFRESH PUBLICATION';
raise notice 'Subscription % refreshed', sSubName;
end $procedure$
;
--
-- TEST CASE
-- Note: All steps is run under usr_db_deploy (non-superuser)
-- Task: Add a new table into publisher on source side and replicate the table to destination side
--
-- 1. disable subscrition [DESTINATION DATABASE]
\c db2 usr_db_deploy
call test.disable_subscription('test_sub');
-- 2. change: create new table [SOURCE DATABASE]
\c db1 usr_db_deploy
create table test.tab2 (id int primary key, num int);
grant select on table test.tab2 to usr_db_repl;
insert into test.tab2 values (2, 20);
select * from test.tab2;
-- 3. add table into publication [SOURCE DATABASE]
\c db1 usr_db_deploy
alter publication test_pub add table test.tab2;
select * from pg_publication_tables;
-- 4. create new table on destination side [DESTINATION DATABASE]
\c db2 usr_db_deploy
create table test.tab2 (id int primary key, num int);
-- 5. enable subscription [DESTINATION DATABASE]
\c db2 usr_db_deploy
call test.enable_subscription('test_sub');
-- 6. check new table [DESTINATION DATABASE]
-- Note: no rows, we need REFRESH PUBLICATION - this will start replication of tables that were added to the subscribed-to publications
\c db2 usr_db_deploy
select * from test.tab2;
-- 7. refresh subscription [DESTINATION DATABASE]
-- Note: must be run as owner of subscription (superuser) but we need run as non-superuser (usr_db_deploy)
\c db2 usr_db_deploy
call test.refresh_subscription('test_sub');
ERROR: ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function
CONTEXT: SQL statement "alter subscription test_sub REFRESH PUBLICATION"
PL/pgSQL function test.refresh_subscription(character varying) line 4 at EXECUTE
-- 8. manual fix [DESTINATION DATABASE]
-- Note: run refresh as superuser
\c db2 postgres
alter subscription test_sub refresh publication;
select * from test.tab2;
Hello,we test database migration to new db servers from version 12 to 15 and a problem with logical replication stopped us.
In the current code (PGSQL ver 12), we use a function with SECURITY DEFINER for refreshing subscriptions:
---
DECLARE
BEGIN
execute 'alter subscription ' || sSubName || 'REFRESH PUBLICATION';
raise notice 'Subscription % refreshed', sSubName;
END
---
The function is called during a deployment of a new version of our application when a deploy service account (NON-SUPERUSER) ENABLE and REFRESH subscriptions at the end of the deploy.In version 15, unfortunately, we get the following error:
ERROR: ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function
I found only one discussion about this problem, where as a workaround is suggested using of dblink (https://postgrespro.com/list/id/CANaTPsphRF+7k+YANMv8goGu3oQLY9XtACpkec8Ju=mr59GHGA@mail.gmail.com#head)
Is there any other possibility or a recommendation to solve this case?
ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function in all PGSQL versions 14+?
Thanks in advance for your help.
Roman Sindelar
Hi Roman,
We had the same problem and we had to create a SERVER (loopback) and use dblink in order to get in done within a Stored Procedure.
I don’t know if this solution will work in your case but it could be something like this….
CREATE SERVER loopback_dblink
FOREIGN DATA WRAPPER dblink_fdw
OPTIONS (hostaddr '127.0.0.1', dbname 'db2');
GRANT USAGE ON FOREIGN SERVER loopback_dblink TO usr_db_deploy;
\c db2;
CREATE USER MAPPING FOR usr_db_deploy
SERVER loopback_dblink
OPTIONS (user 'usr_db_deploy', password '????');
Then, in the procedure, you can try the following
PERFORM dblink_connect( 'myconn', 'loopback_dblink' );
PERFORM dblink_exec( 'myconn', 'ALTER SUBSCRIPTION ' || sSubName || ' REFRESH PUBLICATION' );
PERFORM dblink_disconnect('myconn');
Regards,
Patrick
From: Roman Šindelář <roman.sindelar@gmail.com>
Sent: Wednesday, December 6, 2023 1:04 PM
To: pgsql-general@lists.postgresql.org
Subject: Re: ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function
Hello,
we prepared a test case to demonstrate our case.
TASK
Update replicated db schema (add new table) without superuser privileges.
PROBLEM
Our steps work in PostgreSQL ver 12 but we get the following error in 15
---
ERROR: ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function
CONTEXT: SQL statement "alter subscription test_sub REFRESH PUBLICATION"
PL/pgSQL function test.refresh_subscription(character varying) line 4 at EXECUTE
---
QUESTIONS
- ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function in all PGSQL versions 14+, is it desired behavior?
- Is there any other possibility or a recommendation to solve our case/task?
TEST CASE
Creates a db environment with two databases, two db users and one replicated table. Then we try to add a new table to the publication and without superuser privileges refresh replicated tables.
--
-- ALTER SUBSCRIPTION REFRESH TEST CASE
--
-- DATABASES
-- db1 - source db
-- db2 - destination db (replica)
--
-- USERS
-- postgres - superuser
-- usr_db_repl - user for replication
-- usr_db_deploy - deploy user
--
-- PG_HBA.CONF
-- host replication usr_db_repl localhost trust
-- host db1, db2 usr_db_repl localhost trust
-- host db1, db2 usr_db_deploy localhost trust
-- local db1, db2 usr_db_deploy trust
--
--
-- PREPARATION TEST ENVIRONMENT
--
-- create users
create user usr_db_repl replication;
create user usr_db_deploy;
-- create databases
create database db1;
grant create on database db1 to usr_db_deploy;
create database db2;
grant create on database db2 to usr_db_deploy;
-- create source table
\c db1 usr_db_deploy
create schema test;
create table test.tab1 (id int primary key, num int);
grant usage on schema test to usr_db_repl;
grant select on table test.tab1 to usr_db_repl;
insert into test.tab1 values (1, 10);
select * from test.tab1;
-- create publication on source side
create publication test_pub for table test.tab1;
select * from pg_publication_tables;
-- create replication slot on source side
\c db1 postgres
select pg_create_logical_replication_slot('test_sub', 'pgoutput');
select * from pg_replication_slots;
-- create table on destination side
\c db2 usr_db_deploy
create schema test;
grant usage on schema test to usr_db_repl;
create table test.tab1 (id int primary key, num int);
-- create subscription on destination side using pre-prepared replication slot
\c db2 postgres
create subscription test_sub connection 'host=localhost port=5432 dbname=db1 user=usr_db_repl' publication test_pub with (slot_name=test_sub, create_slot=false);
select * from pg_subscription;
-- check replicated table
\c db2 usr_db_deploy
select * from test.tab1;
-- create functions with security definer on destination side
\c db2 postgres
create procedure test.disable_subscription(in ssubname character varying)
language plpgsql
security definer
as $procedure$
declare
begin
execute 'alter subscription ' || sSubName || ' disable';
raise notice 'Subscription % refreshed', sSubName;
end $procedure$
;
create procedure test.enable_subscription(in ssubname character varying)
language plpgsql
security definer
as $procedure$
declare
begin
execute 'alter subscription ' || sSubName || ' enable';
raise notice 'Subscription % refreshed', sSubName;
end $procedure$
;
create procedure test.refresh_subscription(in ssubname character varying)
language plpgsql
security definer
as $procedure$
declare
begin
execute 'alter subscription ' || sSubName || ' REFRESH PUBLICATION';
raise notice 'Subscription % refreshed', sSubName;
end $procedure$
;
--
-- TEST CASE
-- Note: All steps is run under usr_db_deploy (non-superuser)
-- Task: Add a new table into publisher on source side and replicate the table to destination side
--
-- 1. disable subscrition [DESTINATION DATABASE]
\c db2 usr_db_deploy
call test.disable_subscription('test_sub');
-- 2. change: create new table [SOURCE DATABASE]
\c db1 usr_db_deploy
create table test.tab2 (id int primary key, num int);
grant select on table test.tab2 to usr_db_repl;
insert into test.tab2 values (2, 20);
select * from test.tab2;
-- 3. add table into publication [SOURCE DATABASE]
\c db1 usr_db_deploy
alter publication test_pub add table test.tab2;
select * from pg_publication_tables;
-- 4. create new table on destination side [DESTINATION DATABASE]
\c db2 usr_db_deploy
create table test.tab2 (id int primary key, num int);
-- 5. enable subscription [DESTINATION DATABASE]
\c db2 usr_db_deploy
call test.enable_subscription('test_sub');
-- 6. check new table [DESTINATION DATABASE]
-- Note: no rows, we need REFRESH PUBLICATION - this will start replication of tables that were added to the subscribed-to publications
\c db2 usr_db_deploy
select * from test.tab2;
-- 7. refresh subscription [DESTINATION DATABASE]
-- Note: must be run as owner of subscription (superuser) but we need run as non-superuser (usr_db_deploy)
\c db2 usr_db_deploy
call test.refresh_subscription('test_sub');
ERROR: ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function
CONTEXT: SQL statement "alter subscription test_sub REFRESH PUBLICATION"
PL/pgSQL function test.refresh_subscription(character varying) line 4 at EXECUTE
-- 8. manual fix [DESTINATION DATABASE]
-- Note: run refresh as superuser
\c db2 postgres
alter subscription test_sub refresh publication;
select * from test.tab2;
Thanks for you help,
Roman
On Tue, Nov 28, 2023 at 5:01 PM Roman Šindelář <roman.sindelar@gmail.com> wrote:
Hello,
we test database migration to new db servers from version 12 to 15 and a problem with logical replication stopped us.
In the current code (PGSQL ver 12), we use a function with SECURITY DEFINER for refreshing subscriptions:
---
DECLARE
BEGIN
execute 'alter subscription ' || sSubName || 'REFRESH PUBLICATION';
raise notice 'Subscription % refreshed', sSubName;
END
---
The function is called during a deployment of a new version of our application when a deploy service account (NON-SUPERUSER) ENABLE and REFRESH subscriptions at the end of the deploy.
In version 15, unfortunately, we get the following error:
ERROR: ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function
I found only one discussion about this problem, where as a workaround is suggested using of dblink (https://postgrespro.com/list/id/CANaTPsphRF+7k+YANMv8goGu3oQLY9XtACpkec8Ju=mr59GHGA@mail.gmail.com#head)
Is there any other possibility or a recommendation to solve this case?
ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function in all PGSQL versions 14+?
Thanks in advance for your help.
Roman Sindelar
=?UTF-8?B?Um9tYW4gxaBpbmRlbMOhxZk=?= <roman.sindelar@gmail.com> writes: > - ALTER SUBSCRIPTION ... REFRESH cannot be executed from a function in all > PGSQL versions 14+, is it desired behavior? It's intentional to get some other benefits, if that's what you mean. See https://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=ce0fdbfe972 regards, tom lane
Thank you for your answers and help,
Roman
--
-- 9. install extension dblink + create function/procedure [DESTINATION DATABASE]
\c db2 postgres
create extension dblink;
create function test.dblink_record_execute(TEXT, TEXT)
RETURNS SETOF record LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $$dblink_record$$
;
create procedure test.dblink_refresh_subscription(sSubName VARCHAR, user_pwd text)
SECURITY DEFINER AS
$$
DECLARE
BEGIN
perform test.dblink_record_execute(
pg_catalog.format('user=%L dbname=%L port=%L password=%L', current_user, pg_catalog.current_database(), (SELECT setting FROM pg_catalog.pg_settings WHERE name = 'port'), user_pwd),
pg_catalog.format('ALTER SUBSCRIPTION %I REFRESH PUBLICATION', sSubName)
);
raise notice 'Subscription % refreshed', sSubName;
END $$ LANGUAGE 'plpgsql';
grant execute on function test.dblink_record_execute(text,text) to usr_db_deploy;
grant execute on procedure test.dblink_refresh_subscription(varchar,text) to usr_db_deploy;
-- 10. disable subscription [DESTINATION DATABASE]
\c db2 usr_db_deploy
call test.disable_subscription('test_sub');
-- 11. change: create new table [SOURCE DATABASE]
\c db1 usr_db_deploy
create table test.tab3 (id int primary key, num int);
grant select on table test.tab3 to usr_db_repl;
insert into test.tab3 values (3, 30);
select * from test.tab3;
-- 12. add table into publication [SOURCE DATABASE]
\c db1 usr_db_deploy
alter publication test_pub add table test.tab3;
select * from pg_publication_tables;
-- 13. create new table on destination side [DESTINATION DATABASE]
\c db2 usr_db_deploy
create table test.tab3 (id int primary key, num int);
-- 14. enable subscription [DESTINATION DATABASE]
\c db2 usr_db_deploy
call test.enable_subscription('test_sub');
-- 15. check new table [DESTINATION DATABASE]
-- Note: no rows, we need REFRESH PUBLICATION
\c db2 usr_db_deploy
select * from test.tab3;
-- 16. refresh subscription [DESTINATION DATABASE]
-- Note: run as non-superuser (usr_db_deploy)
\c db2 usr_db_deploy
call test.dblink_refresh_subscription('test_sub','');