pax_global_header00006660000000000000000000000064135402532110014506gustar00rootroot0000000000000052 comment=65a04bf3b70a3d1b2cc6b85fed8dee2039389fa8 pglogical_ticker-1.4.0/000077500000000000000000000000001354025321100150125ustar00rootroot00000000000000pglogical_ticker-1.4.0/LICENSE000066400000000000000000000020511354025321100160150ustar00rootroot00000000000000Copyright 2018 Enova International, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pglogical_ticker-1.4.0/Makefile000066400000000000000000000017111354025321100164520ustar00rootroot00000000000000# pglogical_ticker/Makefile MODULES = pglogical_ticker REGRESS := 01_create_ext 02_setup 03_deploy 04_add_to_rep \ 05_tick 06_worker 07_handlers 08_reentrance \ 09_1_2_tests 99_cleanup EXTENSION = pglogical_ticker DATA = pglogical_ticker--1.0.sql pglogical_ticker--1.0--1.1.sql \ pglogical_ticker--1.1.sql pglogical_ticker--1.1--1.2.sql \ pglogical_ticker--1.2.sql pglogical_ticker--1.2--1.3.sql \ pglogical_ticker--1.3.sql pglogical_ticker--1.3--1.4.sql \ pglogical_ticker--1.4.sql PGFILEDESC = "pglogical_ticker - Have an accurate view of pglogical replication delay" PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) # Prevent unintentional inheritance of PGSERVICE while running regression suite # with make installcheck. We typically use PGSERVICE in our shell environment but # not for dev. Require instead explicit PGPORT= or PGSERVICE= to do installcheck unexport PGSERVICE pglogical_ticker-1.4.0/README.md000066400000000000000000000117651354025321100163030ustar00rootroot00000000000000## pglogical_ticker Periodically churn an in-replication table using a Postgres background worker to get replication time from standpoint of pglogical provider. # How to use pglogical_ticker ### Installation The functionality of this requires postgres 9.5+ and a working install of pglogical. DEB available on official PGDG repository as postgresql-${PGSQL_VERSION}-pglogical-ticker. See installation instruction on https://wiki.postgresql.org/wiki/Apt Or to build from source: ``` make make install make installcheck # run regression suite ``` Although not strictly required, to get access to the configuration settings of `pglogical_ticker` and to auto-launch the ticker on server restart or a soft crash, add `pglogical_ticker` to `shared_preload_libraries` in your postgresql.conf file: ``` shared_preload_libraries = 'pglogical,pglogical_ticker' #... and whatever others you already may have ``` Once installed, simply run this on the provider and all subscribers: ```sql CREATE EXTENSION pglogical_ticker; ``` ### Deploy ticker tables Deploy the ticker tables. Run this command on the provider only, which will use `pglogical.replicate_ddl_command` to send to subscriber. ```sql SELECT pglogical_ticker.deploy_ticker_tables(); ``` This will add a table for each replication_set. For cascading replication, you can add existing tables to another replication set, that belonging to your 2nd tier subscriber. You pass that set_name to the `deploy` function like so: ```sql SELECT pglogical_ticker.deploy_ticker_tables('my_cascaded_set_name'); ``` Add the ticker tables to replication: ```sql SELECT pglogical_ticker.add_ticker_tables_to_replication(); ``` Again, to add ticker tables to a cascaded replication set: ```sql SELECT pglogical_ticker.add_ticker_tables_to_replication('my_cascaded_set_name'); ``` For any more custom needs than this, you can freely add ticker tables to replication sets as you choose to manually. You can manually try the tick() function if you so choose: ```sql SELECT pglogical_ticker.tick(); ``` ### Configuration This is only supported if you have added `pglogical_ticker` in `shared_preload_libraries` as noted above. - `pglogical_ticker.database`: The database in which to launch the ticker (we currently have no need to support multiple databases, but may add that feature at a later time). The ticker will only auto-launch on restart if this setting is configured. - `pglogical_ticker.naptime`: How frequently the ticker ticks - default 10 seconds - `pglogical_ticker.restart_time`: How many seconds before the ticker auto-restarts, default 10. This is also how long it will take to re-launch after a soft crash, for instance. Set this to -1 to disable. **Be aware** that you cannot use this setting to prevent an already-launched ticker from restarting. Only a server restart will take this new value into account for the ticker backend and prevent it from ever restarting, if that is your desired behavior. ### Launching the ticker As of version 1.4, the ticker will automatically launch upon server load if you have `pglogical_ticker` in `shared_preload_libraries`. Otherwise, this function will launch the ticker, only if there is not already one running: ```sql SELECT pglogical_ticker.launch(); /** It is better to use the following function instead, which automatically checks if the system should have a ticker based on tables existing in replication. (this assumes you don't want a replication stream open with no tables). **/ SELECT pglogical_ticker.launch_if_repset_tables(); ``` The background worker launched either by this function or upon server load will run the function `pglogical_ticker.tick()` every n seconds according to `pglogical_ticker.naptime`. Be sure to use caution in monitoring deployment and running of these background worker processes. To view all ticker tables at once, there are functions you can run to view tables on both provider and subscriber: Tables by replication set (provider): ```sql SELECT * FROM pglogical_ticker.all_repset_tickers(); ``` Tables by pglogical subscription (subscriber): ```sql SELECT * FROM pglogical_ticker.all_subscription_tickers(); ``` # For Developers Help is always wanted to review and improve the BackgroundWorker module. It is directly based on `worker_spi` from Postgres' test suite. It could be improved to use a different username. I would also like it to be written so as to prevent any possibility of launching more than one worker, which currently is only done through the exposed function in the docs `launch()`. As of 1.4, I'm also interested in allowing a clean shutdown with exit code 0, as well as (if safe enough) searching databases for the ticker as opposed to having to configure `pglogical_ticker.database`. The SQL files are maintained separately to make version control much easier to see. Make changes in these folders and then run `pglogical_ticker-sql-maker.sh` to build the extension SQL files. This script will need modification with any new release to properly build new extension files based on any new changes. pglogical_ticker-1.4.0/debian/000077500000000000000000000000001354025321100162345ustar00rootroot00000000000000pglogical_ticker-1.4.0/debian/README.md000066400000000000000000000010051354025321100175070ustar00rootroot00000000000000# Debian/Ubuntu packaging This directory contains the Debian control section for pglogical_ticker packages. ## How to Use This 1. Edit the `debian/changelog` file. 2. Run the following command in the top level source directory to build all source and binary packages. ``` debuild -us -uc ``` ## New major version of PostgreSQL? Install the appropriate development packages. The debian/control file needs to be updated. Use the following command in the top level source directory: ``` pg_buildext updatecontrol ``` pglogical_ticker-1.4.0/debian/changelog000066400000000000000000000022271354025321100201110ustar00rootroot00000000000000pglogical-ticker (1.4.0-1) unstable; urgency=medium * Support ticker auto-restart -- Jeremy Finzel Thu, 12 Sep 2019 10:21:00 -0500 pglogical-ticker (1.3.1-1) unstable; urgency=medium * Fix race conditions in tests -- Jeremy Finzel Wed, 23 Jan 2019 09:56:09 -0600 pglogical-ticker (1.3.0-1) unstable; urgency=medium * Support pg11 -- Jeremy Finzel Mon, 22 Oct 2018 12:11:44 -0500 pglogical-ticker (1.2.0-1) unstable; urgency=medium * Add new replication sets without breakage and support cascading rep -- Jeremy Finzel Mon, 30 Jul 2018 12:36:10 -0500 pglogical-ticker (1.1.0-2) unstable; urgency=medium * Compile each version with correct PG version library -- Jeremy Finzel Thu, 28 Jun 2018 16:53:13 -0500 pglogical-ticker (1.1.0-1) unstable; urgency=medium * Version 1.1.0 from upstream. -- Jeremy Finzel Wed, 27 Jun 2018 14:20:51 -0500 pglogical-ticker (1.0.0-1) unstable; urgency=medium * Version 1.0.0 from upstream. -- Dominic Salvador Mon, 30 Apr 2018 00:00:00 -0500 pglogical_ticker-1.4.0/debian/compat000066400000000000000000000000021354025321100174320ustar00rootroot000000000000009 pglogical_ticker-1.4.0/debian/control000066400000000000000000000027061354025321100176440ustar00rootroot00000000000000Source: pglogical-ticker Section: database Priority: optional Maintainer: Jeremy Finzel Build-Depends: debhelper (>= 9), libpq-dev, postgresql-common, postgresql-server-dev-all Standards-Version: 4.1.3 Homepage: https://github.com/enova/pglogical_ticker Vcs-Git: https://github.com/enova/pglogical_ticker.git Package: postgresql-9.5-pglogical-ticker Architecture: any Depends: postgresql-9.5, postgresql-9.5-pglogical, ${shlibs:Depends}, ${misc:Depends} Description: Have time-based replication delay for pglogical A pglogical extension to obtain time-based replication delay for PostgreSQL 9.5. Package: postgresql-9.6-pglogical-ticker Architecture: any Depends: postgresql-9.6, postgresql-9.6-pglogical, ${shlibs:Depends}, ${misc:Depends} Description: Have time-based replication delay for pglogical A pglogical extension to obtain time-based replication delay for PostgreSQL 9.6. Package: postgresql-10-pglogical-ticker Architecture: any Depends: postgresql-10, postgresql-10-pglogical, ${shlibs:Depends}, ${misc:Depends} Description: Have time-based replication delay for pglogical A pglogical extension to obtain time-based replication delay for PostgreSQL 10. Package: postgresql-11-pglogical-ticker Architecture: any Depends: postgresql-11, postgresql-11-pglogical, ${shlibs:Depends}, ${misc:Depends} Description: Have time-based replication delay for pglogical A pglogical extension to obtain time-based replication delay for PostgreSQL 11. pglogical_ticker-1.4.0/debian/control.in000066400000000000000000000011771354025321100202520ustar00rootroot00000000000000Source: pglogical-ticker Section: database Priority: optional Maintainer: Jeremy Finzel Build-Depends: debhelper (>= 9), libpq-dev, postgresql-common, postgresql-server-dev-all Standards-Version: 4.1.3 Homepage: https://github.com/enova/pglogical_ticker Vcs-Git: https://github.com/enova/pglogical_ticker.git Package: postgresql-PGVERSION-pglogical-ticker Architecture: any Depends: postgresql-PGVERSION, postgresql-PGVERSION-pglogical, ${shlibs:Depends}, ${misc:Depends} Description: Have time-based replication delay for pglogical A pglogical extension to get time-based replication delay for PostgreSQL PGVERSION. pglogical_ticker-1.4.0/debian/copyright000066400000000000000000000042061354025321100201710ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pglogical-ticker Source: https://github.com/enova/pglogical_ticker Files: * Copyright: 2018 Enova International, Inc. 2018 Jeremy Finzel License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Files: debian/* Copyright: 2018 Jeremy Finzel License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". pglogical_ticker-1.4.0/debian/docs000066400000000000000000000000121354025321100171000ustar00rootroot00000000000000README.md pglogical_ticker-1.4.0/debian/pgversions000066400000000000000000000000051354025321100203510ustar00rootroot000000000000009.5+ pglogical_ticker-1.4.0/debian/rules000077500000000000000000000010071354025321100173120ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/postgresql-common/pgxs_debian_control.mk # omit this if the package does not use autoconf override_dh_auto_configure: # nothing to do here override_dh_auto_build: +pg_buildext build build-%v override_dh_auto_test: # nothing to do here, see debian/tests/* instead override_dh_auto_install: +pg_buildext install build-%v postgresql-%v-pglogical-ticker override_dh_installdocs: dh_installdocs --all README.* override_dh_auto_clean: +pg_buildext clean build-%v %: dh $@ pglogical_ticker-1.4.0/debian/source/000077500000000000000000000000001354025321100175345ustar00rootroot00000000000000pglogical_ticker-1.4.0/debian/source/format000066400000000000000000000000141354025321100207420ustar00rootroot000000000000003.0 (quilt) pglogical_ticker-1.4.0/debian/tests/000077500000000000000000000000001354025321100173765ustar00rootroot00000000000000pglogical_ticker-1.4.0/debian/tests/control000066400000000000000000000001251354025321100207770ustar00rootroot00000000000000Depends: @, postgresql-server-dev-all Tests: installcheck Restrictions: allow-stderr pglogical_ticker-1.4.0/debian/tests/installcheck000077500000000000000000000001111354025321100217610ustar00rootroot00000000000000#!/bin/sh pg_buildext -o shared_preload_libraries=pglogical installcheck pglogical_ticker-1.4.0/debian/watch000066400000000000000000000001151354025321100172620ustar00rootroot00000000000000version=4 https://github.com/enova/pglogical_ticker/releases .*/v(.*).tar.gz pglogical_ticker-1.4.0/expected/000077500000000000000000000000001354025321100166135ustar00rootroot00000000000000pglogical_ticker-1.4.0/expected/01_create_ext.out000066400000000000000000000003061354025321100217660ustar00rootroot00000000000000-- Allow running regression suite with upgrade paths \set v `echo ${FROMVERSION:-1.4}` SET client_min_messages = warning; CREATE EXTENSION pglogical; CREATE EXTENSION pglogical_ticker VERSION :'v'; pglogical_ticker-1.4.0/expected/02_setup.out000066400000000000000000000007511354025321100210100ustar00rootroot00000000000000SELECT pglogical.create_node('test','host=localhost') INTO TEMP foonode; DROP TABLE foonode; WITH sets AS ( SELECT 'test'||generate_series AS set_name FROM generate_series(1,8) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result INTO TEMP repsets FROM sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; pglogical_ticker-1.4.0/expected/03_deploy.out000066400000000000000000000001751354025321100211450ustar00rootroot00000000000000SELECT pglogical_ticker.deploy_ticker_tables(); deploy_ticker_tables ---------------------- 11 (1 row) pglogical_ticker-1.4.0/expected/04_add_to_rep.out000066400000000000000000000002551354025321100217510ustar00rootroot00000000000000SELECT pglogical_ticker.add_ticker_tables_to_replication(); add_ticker_tables_to_replication ---------------------------------- 11 (1 row) pglogical_ticker-1.4.0/expected/05_tick.out000066400000000000000000000030441354025321100206030ustar00rootroot00000000000000SET client_min_messages TO WARNING; --Verify manual usage of tick function SELECT pglogical_ticker.tick(); tick ------ (1 row) DROP TABLE IF EXISTS checkit; CREATE TEMP TABLE checkit AS SELECT * FROM pglogical_ticker.test1; SELECT pglogical_ticker.tick(); tick ------ (1 row) SELECT (SELECT source_time FROM pglogical_ticker.test1) > (SELECT source_time FROM checkit) AS time_went_up; time_went_up -------------- t (1 row) SELECT pglogical_ticker.tick(); tick ------ (1 row) SELECT provider_name, set_name, source_time IS NOT NULL AS source_time_is_populated FROM pglogical_ticker.all_repset_tickers(); provider_name | set_name | source_time_is_populated ---------------+---------------------+-------------------------- test | default_insert_only | t test | ddl_sql | t test | test1 | t test | test2 | t test | test3 | t test | test4 | t test | test5 | t test | test6 | t test | test7 | t test | test8 | t (10 rows) --This just is going to return nothing because no subscriptions exist. Would be nice to figure out how to test that. SELECT provider_name, set_name, source_time IS NOT NULL AS source_time_is_populated FROM pglogical_ticker.all_subscription_tickers(); provider_name | set_name | source_time_is_populated ---------------+----------+-------------------------- | | f (1 row) pglogical_ticker-1.4.0/expected/06_worker.out000066400000000000000000000051221354025321100211620ustar00rootroot00000000000000SET client_min_messages TO WARNING; --Discard the results here because pid will always be different CREATE TEMP TABLE worker_pid AS SELECT pglogical_ticker.launch() AS pid; --Sleep 2 - should allow the worker to run the first time SELECT pg_sleep(2); pg_sleep ---------- (1 row) --Capture the current source_time DROP TABLE IF EXISTS checkit; CREATE TEMP TABLE checkit AS SELECT source_time FROM pglogical_ticker.test2; --As of 1.0, naptime is 10 seconds, so the worker should run once again if we sleep for 11 SELECT pg_sleep(11); pg_sleep ---------- (1 row) --Table should now have a greater value for source_time SELECT (SELECT source_time FROM pglogical_ticker.test2) > (SELECT source_time FROM checkit) AS time_went_up; time_went_up -------------- t (1 row) SELECT pg_cancel_backend(pid) FROM worker_pid; pg_cancel_backend ------------------- t (1 row) -- Give it time to die asynchronously SELECT pg_sleep(2); pg_sleep ---------- (1 row) --Try the launch_if_repset_tables function DROP TABLE worker_pid; CREATE TEMP TABLE worker_pid AS SELECT pglogical_ticker.launch_if_repset_tables() AS pid; SELECT pg_sleep(2); pg_sleep ---------- (1 row) SELECT COUNT(1) FROM worker_pid WHERE pid IS NOT NULL; count ------- 1 (1 row) SELECT pg_cancel_backend(pid) FROM worker_pid; pg_cancel_backend ------------------- t (1 row) --Test it does nothing with no tables BEGIN; CREATE OR REPLACE FUNCTION pglogical_ticker.rep_set_remove_table_wrapper(set_name name, relation regclass) RETURNS BOOLEAN LANGUAGE plpgsql AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ DECLARE v_result BOOLEAN; BEGIN IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'replication_set_remove_table') THEN SELECT pglogical.replication_set_remove_table(set_name, relation) INTO v_result; ELSEIF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'replication_set_remove_relation') THEN SELECT pglogical.replication_set_remove_relation(set_name, relation) INTO v_result; END IF; RETURN v_result; END; $function$ ; SELECT pglogical_ticker.rep_set_remove_table_wrapper(rs.set_name, rstw.set_reloid) FROM pglogical_ticker.rep_set_table_wrapper() rstw INNER JOIN pglogical.replication_set rs USING (set_id); rep_set_remove_table_wrapper ------------------------------ t t t t t t t t t t t (11 rows) DROP TABLE worker_pid; CREATE TEMP TABLE worker_pid AS SELECT pglogical_ticker.launch_if_repset_tables() AS pid; SELECT COUNT(1) FROM worker_pid WHERE pid IS NOT NULL; count ------- 0 (1 row) ROLLBACK; pglogical_ticker-1.4.0/expected/07_handlers.out000066400000000000000000000043411354025321100214540ustar00rootroot00000000000000/*** There may be a race condition in WaitForBackgroundWorkerStartup that leads to indeterminate behavior on a bad launch. For this reason, we set log_min_messages to FATAL. These tests then really only ensure that the server lives OK during a bad launch. ***/ SET client_min_messages TO FATAL; --The _launch function is not supposed to be used directly --This tests that stupid things don't do something really bad DROP TABLE IF EXISTS bad_pid; CREATE TEMP TABLE bad_pid AS SELECT pglogical_ticker._launch(9999999::OID) AS pid; --Verify that it exits cleanly if the SQL within the worker errors out --In this case, renaming the function will do it ALTER FUNCTION pglogical_ticker.tick() RENAME TO tick_oops; DROP TABLE IF EXISTS bad_pid_2; CREATE TEMP TABLE bad_pid_2 AS SELECT pglogical_ticker.launch() AS pid; -- Give it time to die asynchronously SELECT pg_sleep(2); pg_sleep ---------- (1 row) -- Fix it ALTER FUNCTION pglogical_ticker.tick_oops() RENAME TO tick; --Verify we can't start multiple workers - the second attempt should return NULL --We know this is imperfect but so long as pglogical_ticker.launch is not executed --at the same exact moment this is good enough insurance for now. --Also, multiple workers still could be running without any bad side effects. --Should be false because the process should start OK SELECT pglogical_ticker.launch() IS NULL AS pid; pid ----- f (1 row) SELECT pg_sleep(1); pg_sleep ---------- (1 row) --Should be true because we already have one running SELECT pglogical_ticker.launch() IS NULL AS next_attempt_no_pid; next_attempt_no_pid --------------------- t (1 row) -- We do this because of race condition above. We may be killing more than one pid WITH canceled AS ( SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%') SELECT (SELECT COUNT(1) FROM canceled) > 0 AS at_least_one_canceled; at_least_one_canceled ----------------------- t (1 row) SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT COUNT(1) AS ticker_still_running FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%'; ticker_still_running ---------------------- 0 (1 row) pglogical_ticker-1.4.0/expected/07_handlers_1.out000066400000000000000000000047231354025321100217000ustar00rootroot00000000000000/*** There may be a race condition in WaitForBackgroundWorkerStartup that leads to indeterminate behavior on a bad launch. For this reason, we set log_min_messages to FATAL. These tests then really only ensure that the server lives OK during a bad launch. ***/ SET client_min_messages TO FATAL; --The _launch function is not supposed to be used directly --This tests that stupid things don't do something really bad DROP TABLE IF EXISTS bad_pid; CREATE TEMP TABLE bad_pid AS SELECT pglogical_ticker._launch(9999999::OID) AS pid; ERROR: could not start background process HINT: More details may be available in the server log. --Verify that it exits cleanly if the SQL within the worker errors out --In this case, renaming the function will do it ALTER FUNCTION pglogical_ticker.tick() RENAME TO tick_oops; DROP TABLE IF EXISTS bad_pid_2; CREATE TEMP TABLE bad_pid_2 AS SELECT pglogical_ticker.launch() AS pid; ERROR: could not start background process HINT: More details may be available in the server log. CONTEXT: SQL function "launch" statement 1 -- Give it time to die asynchronously SELECT pg_sleep(2); pg_sleep ---------- (1 row) -- Fix it ALTER FUNCTION pglogical_ticker.tick_oops() RENAME TO tick; --Verify we can't start multiple workers - the second attempt should return NULL --We know this is imperfect but so long as pglogical_ticker.launch is not executed --at the same exact moment this is good enough insurance for now. --Also, multiple workers still could be running without any bad side effects. --Should be false because the process should start OK SELECT pglogical_ticker.launch() IS NULL AS pid; pid ----- f (1 row) SELECT pg_sleep(1); pg_sleep ---------- (1 row) --Should be true because we already have one running SELECT pglogical_ticker.launch() IS NULL AS next_attempt_no_pid; next_attempt_no_pid --------------------- t (1 row) -- We do this because of race condition above. We may be killing more than one pid WITH canceled AS ( SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%') SELECT (SELECT COUNT(1) FROM canceled) > 0 AS at_least_one_canceled; at_least_one_canceled ----------------------- t (1 row) SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT COUNT(1) AS ticker_still_running FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%'; ticker_still_running ---------------------- 0 (1 row) pglogical_ticker-1.4.0/expected/07_handlers_2.out000066400000000000000000000045601354025321100217000ustar00rootroot00000000000000/*** There may be a race condition in WaitForBackgroundWorkerStartup that leads to indeterminate behavior on a bad launch. For this reason, we set log_min_messages to FATAL. These tests then really only ensure that the server lives OK during a bad launch. ***/ SET client_min_messages TO FATAL; --The _launch function is not supposed to be used directly --This tests that stupid things don't do something really bad DROP TABLE IF EXISTS bad_pid; CREATE TEMP TABLE bad_pid AS SELECT pglogical_ticker._launch(9999999::OID) AS pid; --Verify that it exits cleanly if the SQL within the worker errors out --In this case, renaming the function will do it ALTER FUNCTION pglogical_ticker.tick() RENAME TO tick_oops; DROP TABLE IF EXISTS bad_pid_2; CREATE TEMP TABLE bad_pid_2 AS SELECT pglogical_ticker.launch() AS pid; ERROR: could not start background process HINT: More details may be available in the server log. CONTEXT: SQL function "launch" statement 1 -- Give it time to die asynchronously SELECT pg_sleep(2); pg_sleep ---------- (1 row) -- Fix it ALTER FUNCTION pglogical_ticker.tick_oops() RENAME TO tick; --Verify we can't start multiple workers - the second attempt should return NULL --We know this is imperfect but so long as pglogical_ticker.launch is not executed --at the same exact moment this is good enough insurance for now. --Also, multiple workers still could be running without any bad side effects. --Should be false because the process should start OK SELECT pglogical_ticker.launch() IS NULL AS pid; pid ----- f (1 row) SELECT pg_sleep(1); pg_sleep ---------- (1 row) --Should be true because we already have one running SELECT pglogical_ticker.launch() IS NULL AS next_attempt_no_pid; next_attempt_no_pid --------------------- t (1 row) -- We do this because of race condition above. We may be killing more than one pid WITH canceled AS ( SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%') SELECT (SELECT COUNT(1) FROM canceled) > 0 AS at_least_one_canceled; at_least_one_canceled ----------------------- t (1 row) SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT COUNT(1) AS ticker_still_running FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%'; ticker_still_running ---------------------- 0 (1 row) pglogical_ticker-1.4.0/expected/07_handlers_3.out000066400000000000000000000045041354025321100216770ustar00rootroot00000000000000/*** There may be a race condition in WaitForBackgroundWorkerStartup that leads to indeterminate behavior on a bad launch. For this reason, we set log_min_messages to FATAL. These tests then really only ensure that the server lives OK during a bad launch. ***/ SET client_min_messages TO FATAL; --The _launch function is not supposed to be used directly --This tests that stupid things don't do something really bad DROP TABLE IF EXISTS bad_pid; CREATE TEMP TABLE bad_pid AS SELECT pglogical_ticker._launch(9999999::OID) AS pid; ERROR: could not start background process HINT: More details may be available in the server log. --Verify that it exits cleanly if the SQL within the worker errors out --In this case, renaming the function will do it ALTER FUNCTION pglogical_ticker.tick() RENAME TO tick_oops; DROP TABLE IF EXISTS bad_pid_2; CREATE TEMP TABLE bad_pid_2 AS SELECT pglogical_ticker.launch() AS pid; -- Give it time to die asynchronously SELECT pg_sleep(2); pg_sleep ---------- (1 row) -- Fix it ALTER FUNCTION pglogical_ticker.tick_oops() RENAME TO tick; --Verify we can't start multiple workers - the second attempt should return NULL --We know this is imperfect but so long as pglogical_ticker.launch is not executed --at the same exact moment this is good enough insurance for now. --Also, multiple workers still could be running without any bad side effects. --Should be false because the process should start OK SELECT pglogical_ticker.launch() IS NULL AS pid; pid ----- f (1 row) SELECT pg_sleep(1); pg_sleep ---------- (1 row) --Should be true because we already have one running SELECT pglogical_ticker.launch() IS NULL AS next_attempt_no_pid; next_attempt_no_pid --------------------- t (1 row) -- We do this because of race condition above. We may be killing more than one pid WITH canceled AS ( SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%') SELECT (SELECT COUNT(1) FROM canceled) > 0 AS at_least_one_canceled; at_least_one_canceled ----------------------- t (1 row) SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT COUNT(1) AS ticker_still_running FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%'; ticker_still_running ---------------------- 0 (1 row) pglogical_ticker-1.4.0/expected/08_reentrance.out000066400000000000000000000014041354025321100220000ustar00rootroot00000000000000WITH sets AS ( SELECT 'test'||generate_series AS set_name FROM generate_series(9,10) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result INTO TEMP repsets FROM sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; SET client_min_messages TO warning; ALTER EXTENSION pglogical_ticker UPDATE; SELECT pglogical_ticker.deploy_ticker_tables(); deploy_ticker_tables ---------------------- 13 (1 row) SELECT pglogical_ticker.add_ticker_tables_to_replication(); add_ticker_tables_to_replication ---------------------------------- 2 (1 row) pglogical_ticker-1.4.0/expected/09_1_2_tests.out000066400000000000000000000071341354025321100214640ustar00rootroot00000000000000SET client_min_messages TO warning; WITH sets AS ( SELECT 'test'||generate_series AS set_name FROM generate_series(11,12) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result INTO TEMP repsets FROM sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; /*** PRIOR TO 1.2, THIS WOULD SHOW THE FOLLOWING ERROR ERROR: relation "pglogical_ticker.test11" does not exist LINE 2: INSERT INTO pglogical_ticker.test11 (provider_name, sour... ^ QUERY: INSERT INTO pglogical_ticker.test11 (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = 'test11' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); CONTEXT: PL/pgSQL function pglogical_ticker.tick() line 23 at EXECUTE ***/ SELECT pglogical_ticker.tick(); tick ------ (1 row) SELECT * FROM pglogical_ticker.eligible_tickers() ORDER BY set_name, tablename; set_name | tablename ---------------------+--------------------- ddl_sql | ddl_sql default | default default_insert_only | default_insert_only test1 | test1 test10 | test10 test11 | test11 test12 | test12 test2 | test2 test3 | test3 test4 | test4 test5 | test5 test6 | test6 test7 | test7 test8 | test8 test9 | test9 (15 rows) SELECT * FROM pglogical_ticker.eligible_tickers('test1') ORDER BY set_name, tablename; set_name | tablename ----------+--------------------- test1 | ddl_sql test1 | default test1 | default_insert_only test1 | test1 test1 | test10 test1 | test2 test1 | test3 test1 | test4 test1 | test5 test1 | test6 test1 | test7 test1 | test8 test1 | test9 (13 rows) SELECT pglogical_ticker.deploy_ticker_tables('test1'); deploy_ticker_tables ---------------------- 13 (1 row) SELECT pglogical_ticker.add_ticker_tables_to_replication('test1'); add_ticker_tables_to_replication ---------------------------------- 12 (1 row) SELECT set_name, set_reloid FROM pglogical_ticker.rep_set_table_wrapper() rst INNER JOIN pglogical.replication_set rs USING (set_id) INNER JOIN pg_class c ON c.oid = rst.set_reloid -- There is a sorting indeterminacy with quoted table name. So just ignore the 'default' table WHERE set_name = 'test1' AND c.relname <> 'default' ORDER BY set_name, set_reloid::TEXT; set_name | set_reloid ----------+-------------------------------------- test1 | pglogical_ticker.ddl_sql test1 | pglogical_ticker.default_insert_only test1 | pglogical_ticker.test1 test1 | pglogical_ticker.test10 test1 | pglogical_ticker.test2 test1 | pglogical_ticker.test3 test1 | pglogical_ticker.test4 test1 | pglogical_ticker.test5 test1 | pglogical_ticker.test6 test1 | pglogical_ticker.test7 test1 | pglogical_ticker.test8 test1 | pglogical_ticker.test9 (12 rows) --tables are extension members DROP TABLE pglogical_ticker.test1; ERROR: cannot drop table pglogical_ticker.test1 because extension pglogical_ticker requires it HINT: You can drop extension pglogical_ticker instead. pglogical_ticker-1.4.0/expected/99_cleanup.out000066400000000000000000000001471354025321100213160ustar00rootroot00000000000000SET client_min_messages TO WARNING; DROP EXTENSION pglogical_ticker CASCADE; DROP EXTENSION pglogical; pglogical_ticker-1.4.0/functions/000077500000000000000000000000001354025321100170225ustar00rootroot00000000000000pglogical_ticker-1.4.0/functions/pglogical_ticker._launch.sql000066400000000000000000000002521354025321100244540ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker._launch(oid) RETURNS integer LANGUAGE c STRICT AS '$libdir/pglogical_ticker', $function$pglogical_ticker_launch$function$ ;pglogical_ticker-1.4.0/functions/pglogical_ticker.add_ext_object.sql000066400000000000000000000003571354025321100260070ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.add_ext_object(p_type text, p_full_obj_name text) RETURNS void LANGUAGE plpgsql AS $function$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $function$ ;pglogical_ticker-1.4.0/functions/pglogical_ticker.add_ticker_tables_to_replication.sql000066400000000000000000000021441354025321100315630ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM et.set_name, pglogical.replication_set_add_table( set_name:=et.set_name ,relation:=('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name) et WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs ON rs.set_id = rsr.set_id WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS AND et.set_name = rs.set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; pglogical_ticker-1.4.0/functions/pglogical_ticker.all_repset_tickers.sql000066400000000000000000000013571354025321100267300ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.all_repset_tickers() RETURNS TABLE(provider_name name, set_name name, source_time timestamp with time zone) LANGUAGE plpgsql AS $function$ DECLARE v_sql TEXT; BEGIN SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(rs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN pglogical.replication_set rs ON rs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $function$ ;pglogical_ticker-1.4.0/functions/pglogical_ticker.all_subscription_tickers.sql000066400000000000000000000015321354025321100301450ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.all_subscription_tickers() RETURNS TABLE(provider_name name, set_name name, source_time timestamp with time zone) LANGUAGE plpgsql AS $function$ DECLARE v_sql TEXT; BEGIN WITH sub_rep_sets AS ( SELECT DISTINCT unnest(sub_replication_sets) AS set_name FROM pglogical.subscription ) SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(srs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN sub_rep_sets srs ON srs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $function$ ;pglogical_ticker-1.4.0/functions/pglogical_ticker.deploy_ticker_tables.sql000066400000000000000000000017621354025321100272410ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.deploy_ticker_tables( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(tablename)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ ); SELECT pglogical_ticker.add_ext_object( 'TABLE', format('%s.%s', 'pglogical_ticker', quote_ident($$||quote_literal(tablename)||$$) ) ); $$, ARRAY[set_name]) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; pglogical_ticker-1.4.0/functions/pglogical_ticker.drop_ext_object.sql000066400000000000000000000003611354025321100262160ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.drop_ext_object(p_type text, p_full_obj_name text) RETURNS void LANGUAGE plpgsql AS $function$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $function$ ;pglogical_ticker-1.4.0/functions/pglogical_ticker.eligible_tickers.sql000066400000000000000000000022101354025321100263370ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.eligible_tickers ( /*** "Eligible tickers" are defined as replication sets and tables that are eligible to be created or added to replication, either because the replication sets exist, or with cascading replication, the tables already exist to add to a specified replication set p_cascade_to_set_name as cascaded tickers. ***/ p_cascade_to_set_name NAME = NULL ) RETURNS TABLE (set_name name, tablename name) LANGUAGE plpgsql AS $function$ /**** It assumes this extension is installed both places! */ BEGIN RETURN QUERY --In the generic case, always tablename = set_name SELECT rs.set_name, rs.set_name AS tablename FROM pglogical.replication_set rs WHERE p_cascade_to_set_name IS NULL UNION --For cascading replication, we override set_name SELECT p_cascade_to_set_name AS set_name_out, relname AS tablename FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE p_cascade_to_set_name IS NOT NULL AND n.nspname = 'pglogical_ticker' AND c.relkind = 'r' AND EXISTS ( SELECT 1 FROM pglogical.replication_set rsi WHERE rsi.set_name = p_cascade_to_set_name ); END; $function$ ; pglogical_ticker-1.4.0/functions/pglogical_ticker.launch.sql000066400000000000000000000007141354025321100243200ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.launch() RETURNS integer LANGUAGE sql STRICT AS $function$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND application_name LIKE 'pglogical_ticker%') AND NOT pg_is_in_recovery(); $function$ ; pglogical_ticker-1.4.0/functions/pglogical_ticker.launch_if_repset_tables.sql000066400000000000000000000003471354025321100277140ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.launch_if_repset_tables() RETURNS integer LANGUAGE sql AS $function$ SELECT pglogical_ticker.launch() WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper()); $function$ ;pglogical_ticker-1.4.0/functions/pglogical_ticker.rep_set_table_wrapper.sql000066400000000000000000000015331354025321100274160ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_table r; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_relation r; ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; END; $function$ ; pglogical_ticker-1.4.0/functions/pglogical_ticker.tick.sql000066400000000000000000000027301354025321100240000ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.tick() RETURNS void LANGUAGE plpgsql AS $function$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT rs.set_name FROM pglogical.replication_set rs /*** Don't try to tick tables that don't yet exist. This will allow us to create replication sets without worrying about adding a ticker table immediately. ***/ WHERE EXISTS (SELECT 1 FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'pglogical_ticker' AND c.relname = rs.set_name /*** Also avoid uselessly ticking tables that are not in any replication set (regardless of which one) ***/ AND EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rst WHERE c.oid = rst.set_reloid) ) ORDER BY rs.set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $function$ ; pglogical_ticker-1.4.0/functions/pglogical_ticker.tick_rep_set.sql000066400000000000000000000012531354025321100255200ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $function$ ; pglogical_ticker-1.4.0/functions/pglogical_ticker.toggle_ext_object.sql000066400000000000000000000015531354025321100265370ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object(p_type text, p_full_obj_name text, p_toggle text) RETURNS void LANGUAGE plpgsql AS $function$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; END; $function$ ; pglogical_ticker-1.4.0/pglogical_ticker--1.0--1.1.sql000066400000000000000000000106171354025321100217650ustar00rootroot00000000000000/* pglogical_ticker--1.0--1.1.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit --This must be done AFTER we update the function def SELECT pglogical_ticker.drop_ext_object('FUNCTION','pglogical_ticker.dependency_update()'); DROP FUNCTION pglogical_ticker.dependency_update(); SELECT pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW IF EXISTS pglogical_ticker.rep_set_table_wrapper; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object(p_type text, p_full_obj_name text, p_toggle text) RETURNS void LANGUAGE plpgsql AS $function$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_table r; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_relation r; ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch() RETURNS integer LANGUAGE sql STRICT AS $function$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();') AND NOT pg_is_in_recovery(); $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch_if_repset_tables() RETURNS integer LANGUAGE sql AS $function$ SELECT pglogical_ticker.launch() WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper()); $function$ ; pglogical_ticker-1.4.0/pglogical_ticker--1.0.sql000066400000000000000000000166731354025321100214230ustar00rootroot00000000000000/* pglogical_ticker--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit CREATE FUNCTION pglogical_ticker._launch(oid) RETURNS pg_catalog.INT4 STRICT AS 'MODULE_PATHNAME', 'pglogical_ticker_launch' LANGUAGE C; CREATE FUNCTION pglogical_ticker.launch() RETURNS pg_catalog.INT4 STRICT AS $BODY$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();'); $BODY$ LANGUAGE SQL; CREATE FUNCTION pglogical_ticker.dependency_update() RETURNS VOID AS $DEPS$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pglogical_ticker') THEN PERFORM pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW pglogical_ticker.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; ELSE CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; END IF; END; $DEPS$ LANGUAGE plpgsql; SELECT pglogical_ticker.dependency_update(); CREATE OR REPLACE FUNCTION pglogical_ticker.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object (p_type text , p_full_obj_name text , p_toggle text) RETURNS VOID AS $BODY$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; /*EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; */ END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.deploy_ticker_tables() RETURNS INT AS $BODY$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(set_name)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ );$$, ARRAY[set_name]) FROM pglogical.replication_set; PERFORM pglogical_ticker.add_ext_object('TABLE', 'pglogical_ticker.'||quote_ident(set_name)) FROM pglogical.replication_set; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_repset_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(rs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN pglogical.replication_set rs ON rs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_subscription_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN WITH sub_rep_sets AS ( SELECT DISTINCT unnest(sub_replication_sets) AS set_name FROM pglogical.subscription ) SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(srs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN sub_rep_sets srs ON srs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS INT AS $BODY$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS INT AS $BODY$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick() RETURNS VOID AS $BODY$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT set_name FROM pglogical.replication_set ORDER BY set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $BODY$ LANGUAGE plpgsql; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical_ticker FROM PUBLIC; pglogical_ticker-1.4.0/pglogical_ticker--1.1--1.2.sql000066400000000000000000000117621354025321100217710ustar00rootroot00000000000000/* pglogical_ticker--1.1--1.2.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit DROP FUNCTION pglogical_ticker.deploy_ticker_tables(); DROP FUNCTION pglogical_ticker.add_ticker_tables_to_replication(); CREATE OR REPLACE FUNCTION pglogical_ticker.eligible_tickers ( /*** "Eligible tickers" are defined as replication sets and tables that are eligible to be created or added to replication, either because the replication sets exist, or with cascading replication, the tables already exist to add to a specified replication set p_cascade_to_set_name as cascaded tickers. ***/ p_cascade_to_set_name NAME = NULL ) RETURNS TABLE (set_name name, tablename name) LANGUAGE plpgsql AS $function$ /**** It assumes this extension is installed both places! */ BEGIN RETURN QUERY --In the generic case, always tablename = set_name SELECT rs.set_name, rs.set_name AS tablename FROM pglogical.replication_set rs WHERE p_cascade_to_set_name IS NULL UNION --For cascading replication, we override set_name SELECT p_cascade_to_set_name AS set_name_out, relname AS tablename FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE p_cascade_to_set_name IS NOT NULL AND n.nspname = 'pglogical_ticker' AND c.relkind = 'r' AND EXISTS ( SELECT 1 FROM pglogical.replication_set rsi WHERE rsi.set_name = p_cascade_to_set_name ); END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.deploy_ticker_tables( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(tablename)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ ); SELECT pglogical_ticker.add_ext_object( 'TABLE', format('%s.%s', 'pglogical_ticker', quote_ident($$||quote_literal(tablename)||$$) ) ); $$, ARRAY[set_name]) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM et.set_name, pglogical.replication_set_add_table( set_name:=et.set_name ,relation:=('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name) et WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs ON rs.set_id = rsr.set_id WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS AND et.set_name = rs.set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.tick() RETURNS void LANGUAGE plpgsql AS $function$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT rs.set_name FROM pglogical.replication_set rs /*** Don't try to tick tables that don't yet exist. This will allow us to create replication sets without worrying about adding a ticker table immediately. ***/ WHERE EXISTS (SELECT 1 FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'pglogical_ticker' AND c.relname = rs.set_name /*** Also avoid uselessly ticking tables that are not in any replication set (regardless of which one) ***/ AND EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rst WHERE c.oid = rst.set_reloid) ) ORDER BY rs.set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $function$ ; pglogical_ticker-1.4.0/pglogical_ticker--1.1.sql000066400000000000000000000275121354025321100214160ustar00rootroot00000000000000/* pglogical_ticker--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit CREATE FUNCTION pglogical_ticker._launch(oid) RETURNS pg_catalog.INT4 STRICT AS 'MODULE_PATHNAME', 'pglogical_ticker_launch' LANGUAGE C; CREATE FUNCTION pglogical_ticker.launch() RETURNS pg_catalog.INT4 STRICT AS $BODY$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();'); $BODY$ LANGUAGE SQL; CREATE FUNCTION pglogical_ticker.dependency_update() RETURNS VOID AS $DEPS$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pglogical_ticker') THEN PERFORM pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW pglogical_ticker.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; ELSE CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; END IF; END; $DEPS$ LANGUAGE plpgsql; SELECT pglogical_ticker.dependency_update(); CREATE OR REPLACE FUNCTION pglogical_ticker.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object (p_type text , p_full_obj_name text , p_toggle text) RETURNS VOID AS $BODY$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; /*EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; */ END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.deploy_ticker_tables() RETURNS INT AS $BODY$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(set_name)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ );$$, ARRAY[set_name]) FROM pglogical.replication_set; PERFORM pglogical_ticker.add_ext_object('TABLE', 'pglogical_ticker.'||quote_ident(set_name)) FROM pglogical.replication_set; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_repset_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(rs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN pglogical.replication_set rs ON rs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_subscription_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN WITH sub_rep_sets AS ( SELECT DISTINCT unnest(sub_replication_sets) AS set_name FROM pglogical.subscription ) SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(srs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN sub_rep_sets srs ON srs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS INT AS $BODY$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS INT AS $BODY$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick() RETURNS VOID AS $BODY$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT set_name FROM pglogical.replication_set ORDER BY set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $BODY$ LANGUAGE plpgsql; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical_ticker FROM PUBLIC; /* pglogical_ticker--1.0--1.1.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit --This must be done AFTER we update the function def SELECT pglogical_ticker.drop_ext_object('FUNCTION','pglogical_ticker.dependency_update()'); DROP FUNCTION pglogical_ticker.dependency_update(); SELECT pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW IF EXISTS pglogical_ticker.rep_set_table_wrapper; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object(p_type text, p_full_obj_name text, p_toggle text) RETURNS void LANGUAGE plpgsql AS $function$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_table r; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_relation r; ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch() RETURNS integer LANGUAGE sql STRICT AS $function$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();') AND NOT pg_is_in_recovery(); $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch_if_repset_tables() RETURNS integer LANGUAGE sql AS $function$ SELECT pglogical_ticker.launch() WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper()); $function$ ; pglogical_ticker-1.4.0/pglogical_ticker--1.2--1.3.sql000066400000000000000000000000001354025321100217520ustar00rootroot00000000000000pglogical_ticker-1.4.0/pglogical_ticker--1.2.sql000066400000000000000000000414741354025321100214220ustar00rootroot00000000000000/* pglogical_ticker--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit CREATE FUNCTION pglogical_ticker._launch(oid) RETURNS pg_catalog.INT4 STRICT AS 'MODULE_PATHNAME', 'pglogical_ticker_launch' LANGUAGE C; CREATE FUNCTION pglogical_ticker.launch() RETURNS pg_catalog.INT4 STRICT AS $BODY$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();'); $BODY$ LANGUAGE SQL; CREATE FUNCTION pglogical_ticker.dependency_update() RETURNS VOID AS $DEPS$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pglogical_ticker') THEN PERFORM pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW pglogical_ticker.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; ELSE CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; END IF; END; $DEPS$ LANGUAGE plpgsql; SELECT pglogical_ticker.dependency_update(); CREATE OR REPLACE FUNCTION pglogical_ticker.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object (p_type text , p_full_obj_name text , p_toggle text) RETURNS VOID AS $BODY$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; /*EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; */ END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.deploy_ticker_tables() RETURNS INT AS $BODY$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(set_name)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ );$$, ARRAY[set_name]) FROM pglogical.replication_set; PERFORM pglogical_ticker.add_ext_object('TABLE', 'pglogical_ticker.'||quote_ident(set_name)) FROM pglogical.replication_set; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_repset_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(rs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN pglogical.replication_set rs ON rs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_subscription_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN WITH sub_rep_sets AS ( SELECT DISTINCT unnest(sub_replication_sets) AS set_name FROM pglogical.subscription ) SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(srs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN sub_rep_sets srs ON srs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS INT AS $BODY$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS INT AS $BODY$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick() RETURNS VOID AS $BODY$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT set_name FROM pglogical.replication_set ORDER BY set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $BODY$ LANGUAGE plpgsql; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical_ticker FROM PUBLIC; /* pglogical_ticker--1.0--1.1.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit --This must be done AFTER we update the function def SELECT pglogical_ticker.drop_ext_object('FUNCTION','pglogical_ticker.dependency_update()'); DROP FUNCTION pglogical_ticker.dependency_update(); SELECT pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW IF EXISTS pglogical_ticker.rep_set_table_wrapper; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object(p_type text, p_full_obj_name text, p_toggle text) RETURNS void LANGUAGE plpgsql AS $function$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_table r; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_relation r; ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch() RETURNS integer LANGUAGE sql STRICT AS $function$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();') AND NOT pg_is_in_recovery(); $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch_if_repset_tables() RETURNS integer LANGUAGE sql AS $function$ SELECT pglogical_ticker.launch() WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper()); $function$ ; /* pglogical_ticker--1.1--1.2.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit DROP FUNCTION pglogical_ticker.deploy_ticker_tables(); DROP FUNCTION pglogical_ticker.add_ticker_tables_to_replication(); CREATE OR REPLACE FUNCTION pglogical_ticker.eligible_tickers ( /*** "Eligible tickers" are defined as replication sets and tables that are eligible to be created or added to replication, either because the replication sets exist, or with cascading replication, the tables already exist to add to a specified replication set p_cascade_to_set_name as cascaded tickers. ***/ p_cascade_to_set_name NAME = NULL ) RETURNS TABLE (set_name name, tablename name) LANGUAGE plpgsql AS $function$ /**** It assumes this extension is installed both places! */ BEGIN RETURN QUERY --In the generic case, always tablename = set_name SELECT rs.set_name, rs.set_name AS tablename FROM pglogical.replication_set rs WHERE p_cascade_to_set_name IS NULL UNION --For cascading replication, we override set_name SELECT p_cascade_to_set_name AS set_name_out, relname AS tablename FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE p_cascade_to_set_name IS NOT NULL AND n.nspname = 'pglogical_ticker' AND c.relkind = 'r' AND EXISTS ( SELECT 1 FROM pglogical.replication_set rsi WHERE rsi.set_name = p_cascade_to_set_name ); END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.deploy_ticker_tables( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(tablename)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ ); SELECT pglogical_ticker.add_ext_object( 'TABLE', format('%s.%s', 'pglogical_ticker', quote_ident($$||quote_literal(tablename)||$$) ) ); $$, ARRAY[set_name]) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM et.set_name, pglogical.replication_set_add_table( set_name:=et.set_name ,relation:=('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name) et WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs ON rs.set_id = rsr.set_id WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS AND et.set_name = rs.set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.tick() RETURNS void LANGUAGE plpgsql AS $function$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT rs.set_name FROM pglogical.replication_set rs /*** Don't try to tick tables that don't yet exist. This will allow us to create replication sets without worrying about adding a ticker table immediately. ***/ WHERE EXISTS (SELECT 1 FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'pglogical_ticker' AND c.relname = rs.set_name /*** Also avoid uselessly ticking tables that are not in any replication set (regardless of which one) ***/ AND EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rst WHERE c.oid = rst.set_reloid) ) ORDER BY rs.set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $function$ ; pglogical_ticker-1.4.0/pglogical_ticker--1.3--1.4.sql000066400000000000000000000012071354025321100217660ustar00rootroot00000000000000/* pglogical_ticker--1.3--1.4.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit CREATE OR REPLACE FUNCTION pglogical_ticker.launch() RETURNS integer LANGUAGE sql STRICT AS $function$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND application_name LIKE 'pglogical_ticker%') AND NOT pg_is_in_recovery(); $function$ ; pglogical_ticker-1.4.0/pglogical_ticker--1.3.sql000066400000000000000000000414741354025321100214230ustar00rootroot00000000000000/* pglogical_ticker--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit CREATE FUNCTION pglogical_ticker._launch(oid) RETURNS pg_catalog.INT4 STRICT AS 'MODULE_PATHNAME', 'pglogical_ticker_launch' LANGUAGE C; CREATE FUNCTION pglogical_ticker.launch() RETURNS pg_catalog.INT4 STRICT AS $BODY$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();'); $BODY$ LANGUAGE SQL; CREATE FUNCTION pglogical_ticker.dependency_update() RETURNS VOID AS $DEPS$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pglogical_ticker') THEN PERFORM pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW pglogical_ticker.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; ELSE CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; END IF; END; $DEPS$ LANGUAGE plpgsql; SELECT pglogical_ticker.dependency_update(); CREATE OR REPLACE FUNCTION pglogical_ticker.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object (p_type text , p_full_obj_name text , p_toggle text) RETURNS VOID AS $BODY$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; /*EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; */ END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.deploy_ticker_tables() RETURNS INT AS $BODY$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(set_name)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ );$$, ARRAY[set_name]) FROM pglogical.replication_set; PERFORM pglogical_ticker.add_ext_object('TABLE', 'pglogical_ticker.'||quote_ident(set_name)) FROM pglogical.replication_set; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_repset_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(rs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN pglogical.replication_set rs ON rs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_subscription_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN WITH sub_rep_sets AS ( SELECT DISTINCT unnest(sub_replication_sets) AS set_name FROM pglogical.subscription ) SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(srs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN sub_rep_sets srs ON srs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS INT AS $BODY$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS INT AS $BODY$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick() RETURNS VOID AS $BODY$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT set_name FROM pglogical.replication_set ORDER BY set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $BODY$ LANGUAGE plpgsql; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical_ticker FROM PUBLIC; /* pglogical_ticker--1.0--1.1.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit --This must be done AFTER we update the function def SELECT pglogical_ticker.drop_ext_object('FUNCTION','pglogical_ticker.dependency_update()'); DROP FUNCTION pglogical_ticker.dependency_update(); SELECT pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW IF EXISTS pglogical_ticker.rep_set_table_wrapper; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object(p_type text, p_full_obj_name text, p_toggle text) RETURNS void LANGUAGE plpgsql AS $function$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_table r; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_relation r; ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch() RETURNS integer LANGUAGE sql STRICT AS $function$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();') AND NOT pg_is_in_recovery(); $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch_if_repset_tables() RETURNS integer LANGUAGE sql AS $function$ SELECT pglogical_ticker.launch() WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper()); $function$ ; /* pglogical_ticker--1.1--1.2.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit DROP FUNCTION pglogical_ticker.deploy_ticker_tables(); DROP FUNCTION pglogical_ticker.add_ticker_tables_to_replication(); CREATE OR REPLACE FUNCTION pglogical_ticker.eligible_tickers ( /*** "Eligible tickers" are defined as replication sets and tables that are eligible to be created or added to replication, either because the replication sets exist, or with cascading replication, the tables already exist to add to a specified replication set p_cascade_to_set_name as cascaded tickers. ***/ p_cascade_to_set_name NAME = NULL ) RETURNS TABLE (set_name name, tablename name) LANGUAGE plpgsql AS $function$ /**** It assumes this extension is installed both places! */ BEGIN RETURN QUERY --In the generic case, always tablename = set_name SELECT rs.set_name, rs.set_name AS tablename FROM pglogical.replication_set rs WHERE p_cascade_to_set_name IS NULL UNION --For cascading replication, we override set_name SELECT p_cascade_to_set_name AS set_name_out, relname AS tablename FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE p_cascade_to_set_name IS NOT NULL AND n.nspname = 'pglogical_ticker' AND c.relkind = 'r' AND EXISTS ( SELECT 1 FROM pglogical.replication_set rsi WHERE rsi.set_name = p_cascade_to_set_name ); END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.deploy_ticker_tables( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(tablename)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ ); SELECT pglogical_ticker.add_ext_object( 'TABLE', format('%s.%s', 'pglogical_ticker', quote_ident($$||quote_literal(tablename)||$$) ) ); $$, ARRAY[set_name]) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM et.set_name, pglogical.replication_set_add_table( set_name:=et.set_name ,relation:=('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name) et WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs ON rs.set_id = rsr.set_id WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS AND et.set_name = rs.set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.tick() RETURNS void LANGUAGE plpgsql AS $function$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT rs.set_name FROM pglogical.replication_set rs /*** Don't try to tick tables that don't yet exist. This will allow us to create replication sets without worrying about adding a ticker table immediately. ***/ WHERE EXISTS (SELECT 1 FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'pglogical_ticker' AND c.relname = rs.set_name /*** Also avoid uselessly ticking tables that are not in any replication set (regardless of which one) ***/ AND EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rst WHERE c.oid = rst.set_reloid) ) ORDER BY rs.set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $function$ ; pglogical_ticker-1.4.0/pglogical_ticker--1.4.sql000066400000000000000000000414741354025321100214240ustar00rootroot00000000000000/* pglogical_ticker--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit CREATE FUNCTION pglogical_ticker._launch(oid) RETURNS pg_catalog.INT4 STRICT AS 'MODULE_PATHNAME', 'pglogical_ticker_launch' LANGUAGE C; CREATE FUNCTION pglogical_ticker.launch() RETURNS pg_catalog.INT4 STRICT AS $BODY$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();'); $BODY$ LANGUAGE SQL; CREATE FUNCTION pglogical_ticker.dependency_update() RETURNS VOID AS $DEPS$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pglogical_ticker') THEN PERFORM pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW pglogical_ticker.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; ELSE CREATE VIEW pglogical_ticker.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; END IF; END; $DEPS$ LANGUAGE plpgsql; SELECT pglogical_ticker.dependency_update(); CREATE OR REPLACE FUNCTION pglogical_ticker.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pglogical_ticker.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object (p_type text , p_full_obj_name text , p_toggle text) RETURNS VOID AS $BODY$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; /*EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; */ END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.deploy_ticker_tables() RETURNS INT AS $BODY$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(set_name)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ );$$, ARRAY[set_name]) FROM pglogical.replication_set; PERFORM pglogical_ticker.add_ext_object('TABLE', 'pglogical_ticker.'||quote_ident(set_name)) FROM pglogical.replication_set; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_repset_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(rs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN pglogical.replication_set rs ON rs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.all_subscription_tickers() RETURNS TABLE (provider_name NAME, set_name NAME, source_time TIMESTAMPTZ) AS $BODY$ DECLARE v_sql TEXT; BEGIN WITH sub_rep_sets AS ( SELECT DISTINCT unnest(sub_replication_sets) AS set_name FROM pglogical.subscription ) SELECT COALESCE( string_agg( format( 'SELECT provider_name, %s::NAME AS set_name, source_time FROM %s', quote_literal(srs.set_name), relid::REGCLASS::TEXT ), E'\nUNION ALL\n' ), 'SELECT NULL::NAME, NULL::NAME, NULL::TIMESTAMPTZ') INTO v_sql FROM pg_stat_user_tables st INNER JOIN sub_rep_sets srs ON srs.set_name = st.relname WHERE schemaname = 'pglogical_ticker'; RETURN QUERY EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS INT AS $BODY$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS INT AS $BODY$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pglogical_ticker.tick() RETURNS VOID AS $BODY$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT set_name FROM pglogical.replication_set ORDER BY set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $BODY$ LANGUAGE plpgsql; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical_ticker FROM PUBLIC; /* pglogical_ticker--1.0--1.1.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit --This must be done AFTER we update the function def SELECT pglogical_ticker.drop_ext_object('FUNCTION','pglogical_ticker.dependency_update()'); DROP FUNCTION pglogical_ticker.dependency_update(); SELECT pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW IF EXISTS pglogical_ticker.rep_set_table_wrapper; CREATE OR REPLACE FUNCTION pglogical_ticker.toggle_ext_object(p_type text, p_full_obj_name text, p_toggle text) RETURNS void LANGUAGE plpgsql AS $function$ DECLARE c_valid_types TEXT[] = ARRAY['EVENT TRIGGER','FUNCTION','VIEW','TABLE']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[upper(p_type)] && c_valid_types) THEN RAISE EXCEPTION 'Must pass one of % as 1st arg.', array_to_string(c_valid_types,','); END IF; IF NOT (SELECT ARRAY[upper(p_toggle)] && c_valid_toggles) THEN RAISE EXCEPTION 'Must pass one of % as 3rd arg.', array_to_string(c_valid_toggles,','); END IF; EXECUTE 'ALTER EXTENSION pglogical_ticker '||p_toggle||' '||p_type||' '||p_full_obj_name; EXCEPTION WHEN undefined_function THEN RETURN; WHEN undefined_object THEN RETURN; WHEN object_not_in_prerequisite_state THEN RETURN; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_table r; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id, r.set_reloid FROM pglogical.replication_set_relation r; ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication() RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM rs.set_name, pglogical.replication_set_add_table( set_name:=rs.set_name ,relation:=('pglogical_ticker.'||quote_ident(set_name))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical.replication_set rs WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(set_name))::REGCLASS AND rsr.set_id = rs.set_id); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.tick_rep_set(p_set_name name) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_sql TEXT; BEGIN v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(p_set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_id = rs.set_id) ON CONFLICT (provider_name, replication_set_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch() RETURNS integer LANGUAGE sql STRICT AS $function$ SELECT pglogical_ticker._launch(oid) FROM pg_database WHERE datname = current_database() --This should be improved in the future but should do --the job for now. AND NOT EXISTS (SELECT 1 FROM pg_stat_activity psa WHERE NOT pid = pg_backend_pid() AND query = 'SELECT pglogical_ticker.tick();') AND NOT pg_is_in_recovery(); $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.launch_if_repset_tables() RETURNS integer LANGUAGE sql AS $function$ SELECT pglogical_ticker.launch() WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper()); $function$ ; /* pglogical_ticker--1.1--1.2.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit DROP FUNCTION pglogical_ticker.deploy_ticker_tables(); DROP FUNCTION pglogical_ticker.add_ticker_tables_to_replication(); CREATE OR REPLACE FUNCTION pglogical_ticker.eligible_tickers ( /*** "Eligible tickers" are defined as replication sets and tables that are eligible to be created or added to replication, either because the replication sets exist, or with cascading replication, the tables already exist to add to a specified replication set p_cascade_to_set_name as cascaded tickers. ***/ p_cascade_to_set_name NAME = NULL ) RETURNS TABLE (set_name name, tablename name) LANGUAGE plpgsql AS $function$ /**** It assumes this extension is installed both places! */ BEGIN RETURN QUERY --In the generic case, always tablename = set_name SELECT rs.set_name, rs.set_name AS tablename FROM pglogical.replication_set rs WHERE p_cascade_to_set_name IS NULL UNION --For cascading replication, we override set_name SELECT p_cascade_to_set_name AS set_name_out, relname AS tablename FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE p_cascade_to_set_name IS NOT NULL AND n.nspname = 'pglogical_ticker' AND c.relkind = 'r' AND EXISTS ( SELECT 1 FROM pglogical.replication_set rsi WHERE rsi.set_name = p_cascade_to_set_name ); END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.deploy_ticker_tables( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ /**** This will create the main table on both provider and all subscriber(s) for in use replication sets. It assumes this extension is installed both places. */ DECLARE v_row_count INT; BEGIN PERFORM pglogical.replicate_ddl_command($$ CREATE TABLE IF NOT EXISTS pglogical_ticker.$$||quote_ident(tablename)||$$ ( provider_name NAME PRIMARY KEY, source_time TIMESTAMPTZ ); SELECT pglogical_ticker.add_ext_object( 'TABLE', format('%s.%s', 'pglogical_ticker', quote_ident($$||quote_literal(tablename)||$$) ) ); $$, ARRAY[set_name]) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.add_ticker_tables_to_replication( --For use with cascading replication, you can pass --a set_name in order to add all current subscription tickers --to this replication set p_cascade_to_set_name NAME = NULL ) RETURNS integer LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN /**** This will add all ticker tables to replication if not done already. It assumes of course pglogical_ticker.deploy_ticker_tables() has been run. */ PERFORM et.set_name, pglogical.replication_set_add_table( set_name:=et.set_name ,relation:=('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS --default synchronize_data is false ,synchronize_data:=false ) FROM pglogical_ticker.eligible_tickers(p_cascade_to_set_name) et WHERE NOT EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs ON rs.set_id = rsr.set_id WHERE rsr.set_reloid = ('pglogical_ticker.'||quote_ident(et.tablename))::REGCLASS AND et.set_name = rs.set_name); GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN v_row_count; END; $function$ ; CREATE OR REPLACE FUNCTION pglogical_ticker.tick() RETURNS void LANGUAGE plpgsql AS $function$ DECLARE v_record RECORD; v_sql TEXT; v_row_count INT; BEGIN FOR v_record IN SELECT rs.set_name FROM pglogical.replication_set rs /*** Don't try to tick tables that don't yet exist. This will allow us to create replication sets without worrying about adding a ticker table immediately. ***/ WHERE EXISTS (SELECT 1 FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'pglogical_ticker' AND c.relname = rs.set_name /*** Also avoid uselessly ticking tables that are not in any replication set (regardless of which one) ***/ AND EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rst WHERE c.oid = rst.set_reloid) ) ORDER BY rs.set_name LOOP v_sql:=$$ INSERT INTO pglogical_ticker.$$||quote_ident(v_record.set_name)||$$ (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = '$$||quote_ident(v_record.set_name)||$$' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); $$; EXECUTE v_sql; END LOOP; END; $function$ ; pglogical_ticker-1.4.0/pglogical_ticker-sql-maker.sh000077500000000000000000000016511354025321100225500ustar00rootroot00000000000000#!/usr/bin/env bash set -eu last_version=1.3 new_version=1.4 last_version_file=pglogical_ticker--${last_version}.sql new_version_file=pglogical_ticker--${new_version}.sql update_file=pglogical_ticker--${last_version}--${new_version}.sql rm -f $update_file rm -f $new_version_file create_update_file_with_header() { cat << EOM > $update_file /* $update_file */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pglogical_ticker" to load this file. \quit EOM } add_sql_to_file() { sql=$1 file=$2 echo "$sql" >> $file } add_file() { s=$1 d=$2 (cat "${s}"; echo; echo) >> "$d" } create_update_file_with_header # Add view and function changes add_file functions/pglogical_ticker.launch.sql $update_file # Only copy diff and new files after last version, and add the update script touch $update_file cp $last_version_file $new_version_file #cat $update_file >> $new_version_file pglogical_ticker-1.4.0/pglogical_ticker.c000066400000000000000000000211551354025321100204640ustar00rootroot00000000000000/* ------------------------------------------------------------------------- * * pglogical_ticker.c * Cloned code from worker_spi and modified to accomplish our goals. * * ------------------------------------------------------------------------- */ #include "postgres.h" /* These are always necessary for a bgworker */ #include "miscadmin.h" #include "postmaster/bgworker.h" #include "storage/backendid.h" #include "storage/ipc.h" #include "storage/latch.h" #include "storage/lwlock.h" #include "storage/proc.h" #include "storage/shmem.h" /* these headers are used by this particular worker's code */ #include "access/xact.h" #include "executor/spi.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "pgstat.h" #include "utils/builtins.h" #include "utils/snapmgr.h" #include "tcop/utility.h" /* includes for ticker */ #include "commands/dbcommands.h" PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(pglogical_ticker_launch); void _PG_init(void); void pglogical_ticker_main(Datum) pg_attribute_noreturn(); /* flags set by signal handlers */ static volatile sig_atomic_t got_sighup = false; static volatile sig_atomic_t got_sigterm = false; /* GUC variables */ static int pglogical_ticker_naptime = 10; static char *pglogical_ticker_database; static int pglogical_ticker_restart_time = 10; /* Constants */ static int pglogical_ticker_total_workers = 1; /* * Signal handler for SIGTERM * Set a flag to let the main loop to terminate, and set our latch to wake * it up. */ static void pglogical_ticker_sigterm(SIGNAL_ARGS) { int save_errno = errno; got_sigterm = true; SetLatch(MyLatch); errno = save_errno; } /* * Signal handler for SIGHUP * Set a flag to tell the main loop to reread the config file, and set * our latch to wake it up. */ static void pglogical_ticker_sighup(SIGNAL_ARGS) { int save_errno = errno; got_sighup = true; SetLatch(MyLatch); errno = save_errno; } void pglogical_ticker_main(Datum main_arg) { Oid db_oid_main = DatumGetObjectId(main_arg); StringInfoData buf; /* Establish signal handlers before unblocking signals. */ pqsignal(SIGHUP, pglogical_ticker_sighup); pqsignal(SIGTERM, pglogical_ticker_sigterm); /* We're now ready to receive signals */ BackgroundWorkerUnblockSignals(); /* Connect to our database */ if (pglogical_ticker_database != NULL) { #if PG_VERSION_NUM >= 110000 BackgroundWorkerInitializeConnection(pglogical_ticker_database, NULL, 0); #else BackgroundWorkerInitializeConnection(pglogical_ticker_database, NULL); #endif } else { #if PG_VERSION_NUM >= 110000 BackgroundWorkerInitializeConnectionByOid(db_oid_main, InvalidOid, 0); #else BackgroundWorkerInitializeConnectionByOid(db_oid_main, InvalidOid); #endif } SetConfigOption("application_name", MyBgworkerEntry->bgw_name, PGC_USERSET, PGC_S_SESSION); elog(LOG, "%s initialized", MyBgworkerEntry->bgw_name); initStringInfo(&buf); appendStringInfo(&buf, "SELECT pglogical_ticker.tick();"); /* * Main loop: do this until the SIGTERM handler tells us to terminate */ while (!got_sigterm) { int rc; /* * Background workers mustn't call usleep() or any direct equivalent: * instead, they may wait on their process latch, which sleeps as * necessary, but is awakened if postmaster dies. That way the * background process goes away immediately in an emergency. */ #if PG_VERSION_NUM >= 100000 rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, pglogical_ticker_naptime * 1000L, PG_WAIT_EXTENSION); #else rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, pglogical_ticker_naptime * 1000L); #endif ResetLatch(MyLatch); /* emergency bailout if postmaster has died */ if (rc & WL_POSTMASTER_DEATH) proc_exit(1); CHECK_FOR_INTERRUPTS(); /* * In case of a SIGHUP, just reload the configuration. */ if (got_sighup) { got_sighup = false; ProcessConfigFile(PGC_SIGHUP); } /* * Start a transaction on which we can run queries. Note that each * StartTransactionCommand() call should be preceded by a * SetCurrentStatementStartTimestamp() call, which sets both the time * for the statement we're about the run, and also the transaction * start time. Also, each other query sent to SPI should probably be * preceded by SetCurrentStatementStartTimestamp(), so that statement * start time is always up to date. * * The SPI_connect() call lets us run queries through the SPI manager, * and the PushActiveSnapshot() call creates an "active" snapshot * which is necessary for queries to have MVCC data to work on. * * The pgstat_report_activity() call makes our activity visible * through the pgstat views. */ SetCurrentStatementStartTimestamp(); StartTransactionCommand(); SPI_connect(); PushActiveSnapshot(GetTransactionSnapshot()); pgstat_report_activity(STATE_RUNNING, buf.data); /* We can now execute queries via SPI */ SPI_execute(buf.data, false, 0); /* * And finish our transaction. */ SPI_finish(); PopActiveSnapshot(); CommitTransactionCommand(); pgstat_report_stat(false); pgstat_report_activity(STATE_IDLE, NULL); } proc_exit(1); } /* * Entrypoint of this module. * * We register more than one worker process here, to demonstrate how that can * be done. */ void _PG_init(void) { BackgroundWorker worker; unsigned int i; /* get the configuration */ DefineCustomIntVariable("pglogical_ticker.naptime", "Duration between each tick (in seconds).", NULL, &pglogical_ticker_naptime, pglogical_ticker_naptime, 1, INT_MAX, PGC_SIGHUP, 0, NULL, NULL, NULL); DefineCustomStringVariable("pglogical_ticker.database", "Database to connect to.", NULL, &pglogical_ticker_database, pglogical_ticker_database, PGC_SIGHUP, 0, NULL, NULL, NULL); DefineCustomIntVariable("pglogical_ticker.restart_time", "Seconds after which to restart ticker if it dies. -1 to disable", NULL, &pglogical_ticker_restart_time, pglogical_ticker_restart_time, -1, INT_MAX, PGC_SIGHUP, 0, NULL, NULL, NULL); if (!process_shared_preload_libraries_in_progress) return; /* Only auto-start worker if pglogical_ticker_database is set */ if (pglogical_ticker_database) { /* set up common data for all our workers */ memset(&worker, 0, sizeof(worker)); worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; worker.bgw_start_time = BgWorkerStart_RecoveryFinished; worker.bgw_restart_time = pglogical_ticker_restart_time; sprintf(worker.bgw_library_name, "pglogical_ticker"); sprintf(worker.bgw_function_name, "pglogical_ticker_main"); worker.bgw_notify_pid = 0; /* * Now fill in worker-specific data, and do the actual registrations. */ for (i = 1; i <= pglogical_ticker_total_workers; i++) { snprintf(worker.bgw_name, BGW_MAXLEN, "pglogical_ticker worker %d", i); #if PG_VERSION_NUM >= 110000 snprintf(worker.bgw_type, BGW_MAXLEN, "pglogical_ticker"); #endif /* Hack to use postgres db oid until we do something smarter */ worker.bgw_main_arg = Int32GetDatum(0); RegisterBackgroundWorker(&worker); } } } /* * Dynamically launch an SPI worker. */ Datum pglogical_ticker_launch(PG_FUNCTION_ARGS) { Oid db_oid = PG_GETARG_OID(0); BackgroundWorker worker; BackgroundWorkerHandle *handle; BgwHandleStatus status; pid_t pid; memset(&worker, 0, sizeof(worker)); worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; worker.bgw_start_time = BgWorkerStart_RecoveryFinished; worker.bgw_restart_time = pglogical_ticker_restart_time; sprintf(worker.bgw_library_name, "pglogical_ticker"); sprintf(worker.bgw_function_name, "pglogical_ticker_main"); snprintf(worker.bgw_name, BGW_MAXLEN, "pglogical_ticker worker"); #if PG_VERSION_NUM >= 110000 snprintf(worker.bgw_type, BGW_MAXLEN, "pglogical_ticker"); #endif worker.bgw_main_arg = ObjectIdGetDatum(db_oid); /* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */ worker.bgw_notify_pid = MyProcPid; if (!RegisterDynamicBackgroundWorker(&worker, &handle)) PG_RETURN_NULL(); status = WaitForBackgroundWorkerStartup(handle, &pid); if (status == BGWH_STOPPED) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("could not start background process"), errhint("More details may be available in the server log."))); if (status == BGWH_POSTMASTER_DIED) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("cannot start background processes without postmaster"), errhint("Kill all remaining database processes and restart the database."))); Assert(status == BGWH_STARTED); PG_RETURN_INT32(pid); } pglogical_ticker-1.4.0/pglogical_ticker.control000066400000000000000000000003521354025321100217160ustar00rootroot00000000000000# pglogical_ticker extension comment = 'Have an accurate view on pglogical replication delay' default_version = '1.4' schema = 'pglogical_ticker' module_pathname = '$libdir/pglogical_ticker' requires = 'pglogical' relocatable = false pglogical_ticker-1.4.0/schema/000077500000000000000000000000001354025321100162525ustar00rootroot00000000000000pglogical_ticker-1.4.0/schema/1.1.sql000066400000000000000000000005341354025321100172740ustar00rootroot00000000000000--This must be done AFTER we update the function def SELECT pglogical_ticker.drop_ext_object('FUNCTION','pglogical_ticker.dependency_update()'); DROP FUNCTION pglogical_ticker.dependency_update(); SELECT pglogical_ticker.drop_ext_object('VIEW','pglogical_ticker.rep_set_table_wrapper'); DROP VIEW IF EXISTS pglogical_ticker.rep_set_table_wrapper; pglogical_ticker-1.4.0/sql/000077500000000000000000000000001354025321100156115ustar00rootroot00000000000000pglogical_ticker-1.4.0/sql/01_create_ext.sql000066400000000000000000000003061354025321100207540ustar00rootroot00000000000000-- Allow running regression suite with upgrade paths \set v `echo ${FROMVERSION:-1.4}` SET client_min_messages = warning; CREATE EXTENSION pglogical; CREATE EXTENSION pglogical_ticker VERSION :'v'; pglogical_ticker-1.4.0/sql/02_setup.sql000066400000000000000000000007541354025321100200010ustar00rootroot00000000000000SELECT pglogical.create_node('test','host=localhost') INTO TEMP foonode; DROP TABLE foonode; WITH sets AS ( SELECT 'test'||generate_series AS set_name FROM generate_series(1,8) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result INTO TEMP repsets FROM sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; pglogical_ticker-1.4.0/sql/03_deploy.sql000066400000000000000000000000601354025321100201240ustar00rootroot00000000000000SELECT pglogical_ticker.deploy_ticker_tables(); pglogical_ticker-1.4.0/sql/04_add_to_rep.sql000066400000000000000000000000741354025321100207360ustar00rootroot00000000000000SELECT pglogical_ticker.add_ticker_tables_to_replication(); pglogical_ticker-1.4.0/sql/05_tick.sql000066400000000000000000000013671354025321100175770ustar00rootroot00000000000000SET client_min_messages TO WARNING; --Verify manual usage of tick function SELECT pglogical_ticker.tick(); DROP TABLE IF EXISTS checkit; CREATE TEMP TABLE checkit AS SELECT * FROM pglogical_ticker.test1; SELECT pglogical_ticker.tick(); SELECT (SELECT source_time FROM pglogical_ticker.test1) > (SELECT source_time FROM checkit) AS time_went_up; SELECT pglogical_ticker.tick(); SELECT provider_name, set_name, source_time IS NOT NULL AS source_time_is_populated FROM pglogical_ticker.all_repset_tickers(); --This just is going to return nothing because no subscriptions exist. Would be nice to figure out how to test that. SELECT provider_name, set_name, source_time IS NOT NULL AS source_time_is_populated FROM pglogical_ticker.all_subscription_tickers(); pglogical_ticker-1.4.0/sql/06_worker.sql000066400000000000000000000042421354025321100201520ustar00rootroot00000000000000SET client_min_messages TO WARNING; --Discard the results here because pid will always be different CREATE TEMP TABLE worker_pid AS SELECT pglogical_ticker.launch() AS pid; --Sleep 2 - should allow the worker to run the first time SELECT pg_sleep(2); --Capture the current source_time DROP TABLE IF EXISTS checkit; CREATE TEMP TABLE checkit AS SELECT source_time FROM pglogical_ticker.test2; --As of 1.0, naptime is 10 seconds, so the worker should run once again if we sleep for 11 SELECT pg_sleep(11); --Table should now have a greater value for source_time SELECT (SELECT source_time FROM pglogical_ticker.test2) > (SELECT source_time FROM checkit) AS time_went_up; SELECT pg_cancel_backend(pid) FROM worker_pid; -- Give it time to die asynchronously SELECT pg_sleep(2); --Try the launch_if_repset_tables function DROP TABLE worker_pid; CREATE TEMP TABLE worker_pid AS SELECT pglogical_ticker.launch_if_repset_tables() AS pid; SELECT pg_sleep(2); SELECT COUNT(1) FROM worker_pid WHERE pid IS NOT NULL; SELECT pg_cancel_backend(pid) FROM worker_pid; --Test it does nothing with no tables BEGIN; CREATE OR REPLACE FUNCTION pglogical_ticker.rep_set_remove_table_wrapper(set_name name, relation regclass) RETURNS BOOLEAN LANGUAGE plpgsql AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical_ticker.rep_set_table_wrapper from version 1 to 2 */ DECLARE v_result BOOLEAN; BEGIN IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'replication_set_remove_table') THEN SELECT pglogical.replication_set_remove_table(set_name, relation) INTO v_result; ELSEIF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'replication_set_remove_relation') THEN SELECT pglogical.replication_set_remove_relation(set_name, relation) INTO v_result; END IF; RETURN v_result; END; $function$ ; SELECT pglogical_ticker.rep_set_remove_table_wrapper(rs.set_name, rstw.set_reloid) FROM pglogical_ticker.rep_set_table_wrapper() rstw INNER JOIN pglogical.replication_set rs USING (set_id); DROP TABLE worker_pid; CREATE TEMP TABLE worker_pid AS SELECT pglogical_ticker.launch_if_repset_tables() AS pid; SELECT COUNT(1) FROM worker_pid WHERE pid IS NOT NULL; ROLLBACK; pglogical_ticker-1.4.0/sql/07_handlers.sql000066400000000000000000000036571354025321100204530ustar00rootroot00000000000000/*** There may be a race condition in WaitForBackgroundWorkerStartup that leads to indeterminate behavior on a bad launch. For this reason, we set log_min_messages to FATAL. These tests then really only ensure that the server lives OK during a bad launch. ***/ SET client_min_messages TO FATAL; --The _launch function is not supposed to be used directly --This tests that stupid things don't do something really bad DROP TABLE IF EXISTS bad_pid; CREATE TEMP TABLE bad_pid AS SELECT pglogical_ticker._launch(9999999::OID) AS pid; --Verify that it exits cleanly if the SQL within the worker errors out --In this case, renaming the function will do it ALTER FUNCTION pglogical_ticker.tick() RENAME TO tick_oops; DROP TABLE IF EXISTS bad_pid_2; CREATE TEMP TABLE bad_pid_2 AS SELECT pglogical_ticker.launch() AS pid; -- Give it time to die asynchronously SELECT pg_sleep(2); -- Fix it ALTER FUNCTION pglogical_ticker.tick_oops() RENAME TO tick; --Verify we can't start multiple workers - the second attempt should return NULL --We know this is imperfect but so long as pglogical_ticker.launch is not executed --at the same exact moment this is good enough insurance for now. --Also, multiple workers still could be running without any bad side effects. --Should be false because the process should start OK SELECT pglogical_ticker.launch() IS NULL AS pid; SELECT pg_sleep(1); --Should be true because we already have one running SELECT pglogical_ticker.launch() IS NULL AS next_attempt_no_pid; -- We do this because of race condition above. We may be killing more than one pid WITH canceled AS ( SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%') SELECT (SELECT COUNT(1) FROM canceled) > 0 AS at_least_one_canceled; SELECT pg_sleep(1); SELECT COUNT(1) AS ticker_still_running FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query LIKE '%pglogical_ticker%'; pglogical_ticker-1.4.0/sql/08_reentrance.sql000066400000000000000000000011121354025321100207620ustar00rootroot00000000000000WITH sets AS ( SELECT 'test'||generate_series AS set_name FROM generate_series(9,10) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result INTO TEMP repsets FROM sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; SET client_min_messages TO warning; ALTER EXTENSION pglogical_ticker UPDATE; SELECT pglogical_ticker.deploy_ticker_tables(); SELECT pglogical_ticker.add_ticker_tables_to_replication(); pglogical_ticker-1.4.0/sql/09_1_2_tests.sql000066400000000000000000000035331354025321100204510ustar00rootroot00000000000000SET client_min_messages TO warning; WITH sets AS ( SELECT 'test'||generate_series AS set_name FROM generate_series(11,12) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result INTO TEMP repsets FROM sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; /*** PRIOR TO 1.2, THIS WOULD SHOW THE FOLLOWING ERROR ERROR: relation "pglogical_ticker.test11" does not exist LINE 2: INSERT INTO pglogical_ticker.test11 (provider_name, sour... ^ QUERY: INSERT INTO pglogical_ticker.test11 (provider_name, source_time) SELECT ni.if_name, now() AS source_time FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE rs.set_name = 'test11' ON CONFLICT (provider_name) DO UPDATE SET source_time = now(); CONTEXT: PL/pgSQL function pglogical_ticker.tick() line 23 at EXECUTE ***/ SELECT pglogical_ticker.tick(); SELECT * FROM pglogical_ticker.eligible_tickers() ORDER BY set_name, tablename; SELECT * FROM pglogical_ticker.eligible_tickers('test1') ORDER BY set_name, tablename; SELECT pglogical_ticker.deploy_ticker_tables('test1'); SELECT pglogical_ticker.add_ticker_tables_to_replication('test1'); SELECT set_name, set_reloid FROM pglogical_ticker.rep_set_table_wrapper() rst INNER JOIN pglogical.replication_set rs USING (set_id) INNER JOIN pg_class c ON c.oid = rst.set_reloid -- There is a sorting indeterminacy with quoted table name. So just ignore the 'default' table WHERE set_name = 'test1' AND c.relname <> 'default' ORDER BY set_name, set_reloid::TEXT; --tables are extension members DROP TABLE pglogical_ticker.test1; pglogical_ticker-1.4.0/sql/99_cleanup.sql000066400000000000000000000001471354025321100203040ustar00rootroot00000000000000SET client_min_messages TO WARNING; DROP EXTENSION pglogical_ticker CASCADE; DROP EXTENSION pglogical; pglogical_ticker-1.4.0/test_all_versions.sh000077500000000000000000000064671354025321100211250ustar00rootroot00000000000000#!/bin/bash set -eu orig_path=$PATH newest_version=1.4 unset PGSERVICE set_path() { version=$1 export PATH=/usr/lib/postgresql/$version/bin:$orig_path } get_port() { version=$1 pg_lsclusters | awk -v version=$version '$1 == version { print $3 }' } make_and_test() { version=$1 from_version=${2:-$newest_version} set_path $version make clean sudo "PATH=$PATH" make uninstall sudo "PATH=$PATH" make install port=$(get_port $version) PGPORT=$port psql postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'contrib_regression' AND pid <> pg_backend_pid()" FROMVERSION=$from_version PGPORT=$port make installcheck sigpg() { sig=$1 echo "performing $sig" sudo systemctl $sig postgresql@${version}-main } # Start without shared_preload_libraries echo "Testing no shared_preload_libraries launch and restart" sudo -u postgres sed -i "s/'pglogical,pglogical_ticker'/'pglogical'/g" /etc/postgresql/$version/main/postgresql.conf sigpg restart ## Run the first 4 regression files which sets things up echo "Seeding with first 4 regression scripts" for f in sql/0[1-4]*; do PGPORT=$port psql contrib_regression -f $f > /dev/null done assert_ticker_running() { PGPORT=$port psql contrib_regression -v "ON_ERROR_STOP" << 'EOM' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_stat_activity WHERE application_name LIKE 'pglogical_ticker%') THEN RAISE EXCEPTION 'No ticker running'; END IF; END$$; EOM echo "PASS" } assert_ticker_not_running() { PGPORT=$port psql contrib_regression -v "ON_ERROR_STOP" << 'EOM' DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_stat_activity WHERE application_name LIKE 'pglogical_ticker%') THEN RAISE EXCEPTION 'Ticker running'; END IF; END$$; EOM echo "PASS" } ticker_check() { echo "Launching ticker if not launched" PGPORT=$port psql contrib_regression -v "ON_ERROR_STOP" << 'EOM' > /dev/null SELECT pglogical_ticker.launch(); SELECT pg_sleep(1); EOM assert_ticker_running echo "Terminating and expecting auto-restart" PGPORT=$port psql contrib_regression -v "ON_ERROR_STOP" << 'EOM' > /dev/null SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE application_name LIKE 'pglogical_ticker%'; SELECT pg_sleep(11); EOM assert_ticker_running } ticker_check # Perform first load using shared_preload_libraries to set GUCs sudo -u postgres sed -i "s/'pglogical'/'pglogical,pglogical_ticker'/g" /etc/postgresql/$version/main/postgresql.conf sudo -u postgres sed -i "\$apglogical_ticker.database = 'contrib_regression'" /etc/postgresql/$version/main/postgresql.conf sigpg reload ticker_check # Restart, now it should auto-launch sigpg restart sleep 11 ticker_check echo "Testing soft crash restart" PGPORT=$port psql contrib_regression -c "SELECT 'i filo postgres'::text, pg_sleep(10);" & pid=`PGPORT=$port psql contrib_regression -Atq -c "SELECT pid FROM pg_stat_activity WHERE NOT pid = pg_backend_pid() AND query ~* 'i filo postgres'"` echo "found pid $pid to kill" sudo kill -9 $pid sleep 12 ticker_check sudo -u postgres sed -i "/pglogical_ticker.database/d" /etc/postgresql/$version/main/postgresql.conf sigpg restart sleep 11 assert_ticker_not_running } test_all_versions() { from_version="$1" cat << EOM *******************FROM VERSION $from_version****************** EOM make_and_test "9.5" make_and_test "9.6" make_and_test "10" make_and_test "11" } test_all_versions "1.4" test_all_versions "1.3" pglogical_ticker-1.4.0/views/000077500000000000000000000000001354025321100161475ustar00rootroot00000000000000pglogical_ticker-1.4.0/views/pglogical_ticker.distinct_sets_in_use.sql000066400000000000000000000006061354025321100264140ustar00rootroot00000000000000CREATE OR REPLACE VIEW pglogical_ticker.distinct_sets_in_use AS SELECT DISTINCT rs.set_name, rs.set_id FROM pglogical.replication_set rs INNER JOIN pglogical.node n ON n.node_id = rs.set_nodeid INNER JOIN pglogical.node_interface ni ON ni.if_nodeid = n.node_id WHERE EXISTS (SELECT 1 FROM pglogical_ticker.rep_set_table_wrapper() rsr WHERE rsr.set_id = rs.set_id) ORDER BY rs.set_name;