pax_global_header00006660000000000000000000000064145317154530014522gustar00rootroot0000000000000052 comment=f6a35350d495f8fcc2bf45d7cee2e45392097314 pgl_ddl_deploy-2.2.1/000077500000000000000000000000001453171545300145055ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/LICENSE000066400000000000000000000020511453171545300155100ustar00rootroot00000000000000Copyright 2017 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. pgl_ddl_deploy-2.2.1/Makefile000066400000000000000000000047721453171545300161570ustar00rootroot00000000000000EXTENSION = pgl_ddl_deploy DATA = pgl_ddl_deploy--1.0.sql pgl_ddl_deploy--1.0--1.1.sql \ pgl_ddl_deploy--1.1.sql pgl_ddl_deploy--1.1--1.2.sql \ pgl_ddl_deploy--1.2.sql pgl_ddl_deploy--1.2--1.3.sql \ pgl_ddl_deploy--1.3.sql pgl_ddl_deploy--1.3--1.4.sql \ pgl_ddl_deploy--1.4.sql pgl_ddl_deploy--1.4--1.5.sql \ pgl_ddl_deploy--1.5.sql pgl_ddl_deploy--1.5--1.6.sql \ pgl_ddl_deploy--1.6.sql pgl_ddl_deploy--1.6--1.7.sql \ pgl_ddl_deploy--1.7.sql pgl_ddl_deploy--1.7--2.0.sql \ pgl_ddl_deploy--2.0.sql pgl_ddl_deploy--2.0--2.1.sql \ pgl_ddl_deploy--2.1.sql pgl_ddl_deploy--2.1--2.2.sql \ pgl_ddl_deploy--2.2.sql MODULES = pgl_ddl_deploy ddl_deparse REGRESS := 01_create_ext 02_setup 03_add_configs 04_deploy 04_deploy_update \ 05_allowed 06_multi 07_edges 08_ignored \ 09_unsupported 10_no_create_user 11_override \ 12_sql_command_tags 13_transaction \ 15_new_set_behavior 16_multi_set_tags \ 17_include_only_repset_tables_1 \ 18_include_only_repset_tables_2 \ 19_include_only_repset_tables_3 \ 20_include_only_repset_tables_4 21_unprivileged_users \ 22_is_deployed 23_1_4_features 24_sub_retries \ 25_1_5_features 26_new_setup \ 27_raise_message 28_1_6_features \ 29_create_ext \ 30_setup \ 31_add_configs \ 32_deploy_update \ 33_allowed \ 34_multi \ 35_edges \ 36_ignored \ 37_unsupported \ 38_no_create_user \ 39_override \ 40_sql_command_tags \ 41_transaction \ 43_new_set_behavior \ 44_multi_set_tags \ 45_include_only_repset_tables_1 \ 46_include_only_repset_tables_2 \ 47_include_only_repset_tables_3 \ 48_include_only_repset_tables_4 \ 49_unprivileged_users \ 50_is_deployed \ 51_1_4_features \ 52_sub_retries \ 53_1_5_features \ 54_new_setup \ 55_raise_message \ 56_1_6_features \ 57_native_features 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 pgl_ddl_deploy-2.2.1/README.md000066400000000000000000001124501453171545300157670ustar00rootroot00000000000000# Transparent Logical DDL Replication (pgl_ddl_deploy) Transparent DDL replication for Postgres 9.5+ for both pglogical and native logical replication. [Overview](#overview) - [Release Notes](#release_notes) - [High Level Description](#high_level) - [Features](#features) - [A Full Example](#full_example) - [Installation](#installation) [Setup and Deployment](#setup) - [Configuration](#config) - [Permissions](#permissions) - [Deployment](#deployment) - [Monitoring and Administration](#monitoring) [Limitations and Restrictions](#limitations) - [DDL involving multiple tables](#multi_tables) - [Unsupported Commands](#unsupported) - [Multi-Statement Client SQL Limitations](#multi_statement) - [Native Logical Supported Configurations](#native_support) [Resolving DDL Replication Issues](#resolve) - [Resolving Failed DDL on Subscribers](#resolve_failed) - [Resolving Unhandled DDL](#resolve_unhandled) - [Disable DDL Replication on Subscriber](#disable_ddl) [For Developers](#devs) - [Help Wanted Features](#help_wanted) - [Regression testing](#regression) # Overview Since the original release of this extension, version 2.0 introduces the major change of support for native logical replication. Read the Original Release Summary here: https://innovation.enova.com/pursuing-postgres-ddl-replication/ # Release Notes ### Release 2.2 Summary of changes: * Support for Postgres 16 ### Release 2.0 Summary of changes: * Support for DDL replication using Native Logical Replication * Support for Postgres 13 ### Release 1.7 Summary of changes: * Support for Postgres 12 * Support for pglogical 2.3.0 ## High Level Description With any current logical replication technology for Postgres, we normally have excellent ways to replicate DML events (`INSERT`, `UPDATE`, `DELETE`), but are left to figure out propagating DDL changes on our own. That is, when we create new tables, alter tables, and the like, we have to manage this separately in our application deployment process in order to make those same changes on logical replicas, and add such tables to replication. As of Postgres 13, there is no native way to do "transparent DDL replication" to other Postgres clusters alongside any logical replication technology, built on standard Postgres. This project is an attempt to do just that. The framework is built on the following concepts: - Event triggers always fire on DDL events, and thus give us immediate access to what we want - Event triggers gives us access (from 9.5+) to what objects are being altered - We can see what SQL the client is executing within an event trigger - We can validate and choose to propagate that SQL statement to subscribers - We can add new tables to replication at the point of creation, prior to any DML execution In many environments, this may cover most if not all DDL statements that are executed in an application environment. We know this doesn't cover 100% of edge cases, but we believe the functionality and robustness is significant enough to add great value in many Postgres environments. It will work especially well in these two use cases: - When you want to replicate all user tables - When you want to replicate only a subset of tables in a schema that will not have any foreign key dependencies to other schemas We also think it's possible to expand this concept by further leveraging the Postgres parser. There is much detail below on what the Limitations and Restrictions are of this framework. ## Features - Any DDL SQL statement can be propagated directly to subscribers without your developers needing to overhaul their migration process or know the intricacies of replication. - Tables can be automatically added to replication upon creation (`include_schema_regex` option). - Filtering by schema (regular expression) is supported. This allows you to selectively replicate only certain schemas within a publication/replication set. - Filtering by a specific set of tables is supported, which is most useful to replicate a small set of tables and maintain things like columns added/dropped - There is an option to deploy in a lock-safe way on subscribers. Note that this means replication will lag until all blockers finish running or are terminated. - There is an option to fail certain events on the subscriber to be retried later. This is useful for example if you are replicating VIEW DDL but do not want that to block replication on failure. - In some edge cases, alerting can be built around provided logging for the DBA to then handle possible manual deployments - `ALTER TABLE` statements can be filtered by subcommand tags. For example, if you are using selective replication and want to ignore things like `DISABLE TRIGGER` which may not exist on the subscriber, this is useful to add robustness to DDL replication. - Optional support for automatically killing blocking processes on the subscriber system that is preventing DDL execution. ## A Full Example Since we always look for documentation by example, we show this first. Assuming logical replication is already setup with an active subscription, and given these publications/replication sets: - `default` - replicate every event - `insert_update` - replicate only inserts and updates Provider: ```sql CREATE EXTENSION pgl_ddl_deploy; --Setup permissions SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname in('app_owner', 'replication_role'); --Setup configs INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('default', '.*', true, true), ('insert_update', '.*happy.*', true, true); ``` Subscribers (run on both `default` and `insert_update` subscribers): ```sql CREATE EXTENSION pgl_ddl_deploy; --Setup permissions for the same role on subscriber to have DDL permissions CREATE ROLE app_owner WITH NOLOGIN; SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname in('app_owner', 'replication_role'); --Be sure that on the subscriber, app_owner role has the following perms: GRANT CREATE ON DATABASE :DBNAME TO app_owner; --If schemas already exist on subscriber that you want the app_owner --role to be able to modify with any DDL, then you must do this: ALTER TABLE foo OWNER TO app_owner; --...etc ``` Here is a way to fix the subscriber table owner based on the tables already in replication on provider for the same publication/replication set and ddl configuration you just setup on provider (shell command - pglogical example): ```sh PGSERVICE=provider_cluster psql provider_db << EOM | PGSERVICE=subscriber_cluster psql subscriber_db COPY ( SELECT 'ALTER TABLE '||quote_ident(n.nspname)||'.'||quote_ident(c.relname)||' OWNER TO app_owner;' FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc ON sc.set_name = rs.set_name INNER JOIN pgl_ddl_deploy.rep_set_table_wrapper() rsr ON rsr.set_id = rs.set_id INNER JOIN pg_class c ON c.oid = rsr.set_reloid INNER JOIN pg_namespace n ON n.oid = c.relnamespace WHERE rs.set_name = 'insert_update' AND n.nspname ~* sc.include_schema_regex AND n.nspname !~* pgl_ddl_deploy.exclude_regex() ) TO STDOUT; EOM ``` Provider: ```sql --Deploy DDL replication SELECT pgl_ddl_deploy.deploy(set_name) FROM pgl_ddl_deploy.set_configs; ``` Subscriber - only required if using native in order to add the `pgl_ddl_deploy.queue` table to replication. ```sql ALTER SUBSCRIPTION default REFRESH PUBLICATION WITH (COPY_DATA = false); ``` Provider: ```sql --App deployment role SET ROLE app_owner; --Let's make some data! CREATE TABLE foo(id serial primary key); ALTER TABLE foo ADD COLUMN bla INT; INSERT INTO foo (bla) VALUES (1),(2),(3); CREATE SCHEMA happy; CREATE TABLE happy.foo(id serial primary key); ``` NOTE - after creating a table using schema-regex-based DDL replication with native logical replication, it is necessary to manually execute on the subscriber the command `SELECT pgl_ddl_deploy.retry_all_subscriber_logs();` to complete the process of adding the new table to replication. This is because of the bug https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com preventing the safety of running ALTER SUBSCRIPTION ... REFRESH PUBLICATION in a replication process. This will be updated as soon as a patch becomes available. ```sql ALTER TABLE happy.foo ADD COLUMN bla INT; INSERT INTO happy.foo (bla) VALUES (1),(2),(3); DELETE FROM happy.foo WHERE bla = 3; ``` Subscriber to `default`: ``` SELECT * FROM foo; id | bla ----+----- 1 | 1 2 | 2 3 | 3 (3 rows) SELECT * FROM happy.foo; id | bla ----+----- 1 | 1 2 | 2 (3 rows) ``` Note that both tables are replicated based on configuration, as are all events (inserts, updates, and deletes). Subscriber to `insert_update`: ``` SELECT * FROM foo; ERROR: relation "foo" does not exist LINE 1: SELECT * FROM foo; SELECT * FROM happy.foo; id | bla ----+----- 1 | 1 2 | 2 3 | 3 (3 rows) ``` Note that the `foo` table (in `public` schema) was not replicated. Also, because we are not replicating deletes here, `happy.foo` still has all data. ## Installation The functionality of this requires postgres version 9.5+ and a working install of pglogical. DEB available on official PGDG repository as ```postgresql-${PGSQL_VERSION}-pgl-ddl-deploy``` see installation instruction on https://wiki.postgresql.org/wiki/Apt See the notes below on requirements to run the regression suite. This extension requires pglogical to be installed before you can create the extension in any database. Then the extension can be deployed as any postgres extension: ```sql CREATE EXTENSION pgl_ddl_deploy; ``` **This extension needs to be installed on provider and all subscribers. As of version 1.5.0, you must have the same pgl_ddl_deploy version on both the provider and subscriber (NOT necessarily the same Postgres version).** To update to pgl_ddl_deploy 1.5 from a previous version, install the latest version packages on your server(s), then run in the database(s): ```sql ALTER EXTENSION pgl_ddl_deploy UPDATE; ``` # Setup and Deployment ## Configuration For native logical replication, DDL replication is configured on a per-publication basis. For pglogical, DDL replication is configured on a per-replication set basis. There are three basic types of configuration: - `include_only_repset_tables` - Only tables already in a publication/replication set are maintained. This means only `ALTER TABLE` or `COMMENT` statements are replicated. - `include_schema_regex` - Provide a regular expression to match both current and future schemas to be automatically added to replication. This supports all event types except for ones like `GRANT` which do not provide access to information about which schema an object exists in. - `include_everything` - Propagate all DDL events regardless of schema. This is for cases like `GRANT` which do not provide access to information about which schema an object exists in The above 3 options are mutually exclusive. You can, however, use an additional option `ddl_only_replication` either with `include_schema_regex` or `include_everything`. This only means tables are not automatically added to replication. Its use is if you want to keep the schema of two systems in sync, but not necessarily replicate data for all tables. Add rows to `pgl_ddl_deploy.set_configs` in order to configure (but not yet deploy) DDL replication for a particular publication/replication set. Note especially that `driver` controls which replication technology this is for, `native` or `pglogical`. For example: ```sql --Only some options are shown. See below for all options --This type of configuration will DDL replicate all user schemas, and auto-add --new matching schemas/tables to replication: INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, driver) VALUES ('default', '.*', 'native'::pgl_ddl_deploy.driver); --This type of configuration will maintain only the specific set of tables --in the given replication set for any `ALTER TABLE` statements: INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_only_repset_tables, driver) VALUES ('my_special_tables', TRUE, 'native'::pgl_ddl_deploy.driver); ``` The relevant settings: - `driver`: `native` or `pglogical` (type `pgl_ddl_deploy.driver`) - DEFAULT pglogical - `set_name`: publication name OR pglogical replication_set name - `include_schema_regex`: a regular expression for which schemas to include in DDL replication. This can be used to auto-add new tables to replication. This option is incompatible with `include_only_repset_tables`. - `lock_safe_deployment`: if true, DDL will execute in a low `lock_timeout` loop on subscriber - `allow_multi_statements`: if true, multiple SQL statements sent by client can be propagated under certain conditions. See below for more details on caveats and edge cases. If false, only a single SQL statement (technically speaking - a SQL statement with a single node `parsetree`) will be eligible for propagation. - `include_only_repset_tables`: if true, only tables that are in replication will be maintained by DDL replication. Thus only `ALTER TABLE` statements are permitted here. This option is incompatible with `include_schema_regex`. - `queue_subscriber_failures`: if true, DDL will be allowed to fail on subscriber without breaking replication, and queued for retry using function `pgl_ddl_deploy.retry_all_subscriber_logs()`. This is useful for example if you are replicating `VIEW` DDL but do not want failures to block data replication. It is **NOT** recommendeded that you use this with any `TABLE` replication, since those events are likely to break data replication. - `create_tags`: the set of command tags for which the create event triggers will fire. Change with caution. These are defaulted to the appropriate default set for either `include_schema_regex` or `include_only_repset_tables`. - `drop_tags`: the set of command tags for which the drop event triggers will fire. Change with caution. These are defaulted to the appropriate default set for either `include_schema_regex` or `include_only_repset_tables`. - `blacklisted_tags`: These are command tags that are never permitted to be propagated to subscribers. It is configurable, but the default is `pgl_ddl_deploy.blacklisted_tags()` - `exclude_alter_table_subcommands`: if you want to exclude certain `ALTER TABLE` subcommand tags, here is the place to do it. The standard list can be found as the function `pgl_ddl_deploy.common_exclude_alter_table_subcommands()`. You can also simply choose only select tags from this list to exclude. - `ddl_only_replication`: for use with `include_schema_regex` only. Allows you to only replicate the schema without auto-adding tables to replication. This is useful in particular if you want to keep the structure of two systems fully synchronized, but you don't necessarily want to replicate data for all tables. - `include_everything`: Propagate all DDL events regardless of schema. This is for cases like `GRANT` which do not provide access to information about which schema an object exists in. - `signal_blocking_subscriber_sessions`: Kill processes on the subscriber holding any kind of lock on the target table which would prevent DDL execution. `cancel` will use `pg_cancel_backend`, `terminate` will use `pg_terminate_backend`. `cancel_then_terminate` will try to cancel and if not successful, will go to terminate. `NULL` disables this feature. Killed sessions will be logged to the subscriber table `pgl_ddl_deploy.killed_blockers`, which has a field `reported` and `reported_at` which are designed for monitoring where you can notify users of killed queries, and then mark those queries as reported to users. ** NOTE ** - currently, we do not support figuring out dependent locks with native partitioning. You may miss killing blocking processes when native partitioning is involved. - `subscriber_lock_timeout`: Only for use with `signal_blocking_subscriber_sessions`. This is an optional parameter for `lock_timeout` for DDL execution on subscriber in milliseconds before killing blockers. Default 3000 (3 seconds). There is already a pattern of schemas excluded always that you need not worry about. You can view them in this function: ```sql SELECT pgl_ddl_deploy.exclude_regex(); ``` You can use this query to check what your current configs will pull in for schemas if you are using `include_schema_regex`: ```sql SELECT sc.set_name, n.nspname FROM pg_namespace n INNER JOIN pgl_ddl_deploy.set_configs sc ON nspname !~* pgl_ddl_deploy.exclude_regex() AND n.nspname ~* sc.include_schema_regex ORDER BY sc.set_name, n.nspname; ``` You can use this query to test a new regex for what existing schemas it matches: ```sql SELECT n.nspname FROM pg_namespace n WHERE nspname !~* pgl_ddl_deploy.exclude_regex() AND n.nspname ~* 'test' ORDER BY n.nspname; ``` There are no stored procedures to insert/update `set_configs`, which we don't think would add much value at this point. There are check constraints and triggers in place to ensure the regex is valid and the other conditions of uniqueness are met for config options. ## Permissions It is important to consider which role will be allowed to run DDL in a given provider. As it stands, this role will need to exist on the subscriber as well, because this same role will be used to try to deploy on the subscriber. pgl_ddl_deploy provides a function to provide permissions needed for a given role to use DDL deployment. This needs to run provider and all subscribers: ```sql SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname IN('app_owner_role'); ``` Note that in upgrading to version 1.5, we are automatically re-applying the permissions you have already granted using `add_role` to the new tables in this version. ## Deployment of Automatic DDL Replication To **deploy** (meaning activate) DDL replication for a given replication set, run: ```sql --Deploy any set_configs with given set_name: SELECT pgl_ddl_deploy.deploy(set_name); --Deploy only a single set_config_id: SELECT pgl_ddl_deploy.deploy(set_config_id); ``` - From this point on, the event triggers are live and will fire on the following events (by default, unless you have customized `create_tags` or `drop_tags`): ``` command_tag ----------------- ALTER FUNCTION ALTER SEQUENCE ALTER TABLE ALTER TYPE ALTER VIEW CREATE FUNCTION CREATE SCHEMA CREATE SEQUENCE CREATE TABLE CREATE TABLE AS CREATE TYPE CREATE VIEW DROP FUNCTION DROP SCHEMA DROP SEQUENCE DROP TABLE DROP TYPE DROP VIEW SELECT INTO ``` - Not all of these events are handled in the same way - see Limitations and Restrictions below - Note that if, based on your configuration, you have tables that *should* be added to replication already, but are not, you will not be allowed to deploy. This is because DDL replication should only be expected to automatically add *new* tables to replication. To override this, add the tables to replication manually and sync as necessary. DDL replication can be disabled/enabled (this will disable/enable event triggers): ```sql --By set_name SELECT pgl_ddl_deploy.disable(set_name); SELECT pgl_ddl_deploy.enable(set_name); --By set_config_id SELECT pgl_ddl_deploy.disable(set_name); SELECT pgl_ddl_deploy.enable(set_name); ``` You can also undeploy DDL replication, which means dropping all event triggers and functions for a given config: ```sql SELECT pgl_ddl_deploy.undeploy(set_name); SELECT pgl_ddl_deploy.undeploy(set_config_id); ``` If you want to **change** the configuration in `set_configs`, you can re-deploy by again running `pgl_ddl_deploy.deploy` on the given `set_name`. There is currently no enforcement/warning if you have changed configuration but not deployed, but should be easy to add such a feature. Note that you are able to override the event triggers completely, for example, if you are an administrator who wants to run DDL and you know you don't want that propagated to subscribers. You can do this with `SESSION_REPLICATION_ROLE`, i.e.: ```sql SET SESSION_REPLICATION_ROLE TO REPLICA; -- I don't care to send this to subscribers (note that you can also exclude statements -- like this by using exclude_alter_table_subcommands) ALTER TABLE foo SET (autovacuum_vacuum_threshold = 1000); RESET SESSION_REPLICATION_ROLE; ``` ## Monitoring and Administration This framework will log all DDL changes that attempt to be propagated. It is also generous in allowing DDL replication procedures to fail in order not to prevent application deployments of DDL. An exception will never be allowed to block a DDL statement. The most that will happen is log_level `WARNING` will be raised. This feature is based on the assumption that we do not want replication issues to ever block client-facing application functionality. Several tables are setup to manage DDL replication and log exceptions, in addition to server log warnings raised at `WARNING` level in case of issues: - `events` - Logs replicated DDL events on the provider - `subscriber_logs` - Logs replicated DDL events executed on subscribers - `commands` - Logs detailed output from `pg_event_trigger_ddl_commands()` and `pg_event_trigger_dropped_objects()` - `unhandled` - Any DDL that is captured but cannot be handled by this framework (see details below) is logged here. - `exceptions` - Any unexpected exception raise by the event trigger functions are logged here There are `resolved` fields on the `unhandled` and `exceptions` tables that can be marked so that monitoring will show only new problems based on this table by using functions: - `pgl_ddl_deploy.resolve_unhandled(unhandled_id INT, notes TEXT = NULL)` - `pgl_ddl_deploy.resolve_exception(exception_id INT, notes TEXT = NULL)` # Limitations and Restrictions ## DDL involving multiple tables A single DDL SQL statement which alters tables both replicated and non-replicated cannot be supported. For example, if I have `include_schema_regex` which includes only the regex `'^replicated.*'`, this is unsupported: ```sql DROP TABLE replicated.foo, notreplicated.bar; ``` Likewise, the following can be problematic if you are using filtered replication: ```sql ALTER TABLE replicated.foo ADD COLUMN foo_id INT REFERENCES unreplicated.foo (id); ``` Depending on your environment, such cases may be very rare, or possibly common. For example, such edge cases are far less likely when you want to do 1:1 replication of just about all tables in your application database. Also, if you are not likely to have relationships between schemas you both are and are not replicating, then edge cases will be unlikely. As mentioned above, this framework will work best with these two use cases: - When you want to replicate all user tables - When you want to replicate only a subset of tables in a schema that will not have any foreign key dependencies to other schemas Some of these edge cases can be minimized with the `exclude_alter_table_subcommands` option, which was developed precisely because for us, the most common failures were things like `DISABLE TRIGGER` for a trigger that does not exist on the subscriber. In this case, the DDL statement could fail on the subscriber. To resolve this, see [Resolving Failed DDL on Subscribers](#resolving_failed). ## Unsupported Commands `CREATE TABLE AS` and `SELECT INTO` are not supported to replicate DDL due to limitations on transactional consistency. That is, if a table is created from a set of data on the provider, to run the same SQL on the subscriber will in no way guarantee consistent data. For example: ```sql CREATE TABLE foo AS SELECT field_1, field_2, now() AS refreshed_at FROM table_1; ``` Not only is it possible that `table_1` doesn't even exist on the subscriber, even if it does it may not be fully up to date with the provider, in which case the data created in the table on the subscriber would not match. Worse, is that the `now()` function is basically guaranteed to be different on the subscriber. It is recommended instead that the DDL statement creating the table and the DML inserting into the table are separated. Continuing the above example: ```sql CREATE TABLE foo (field_1 INT PRIMARY KEY, field_2 TEXT, refreshed_at TIMESTAMPTZ); INSERT INTO foo (field_1, field_2, refreshed_at) SELECT field_1, field_2, now() AS refreshed_at FROM table_1; ``` The above is completely supported by this framework, bearing in mind some of the edge cases with [multi-statements](#multi_statement). The `CREATE TABLE` will automatically be replicated by this framework, and the table will be added to replication since it has a primary key. Then the `INSERT` will be replicated by normal logical replication. **NOTE** that temp tables are not affected by this limitation, since temp objects are always excluded from DDL replication anyway. To resolve these, see [Resolving Unhandled DDL](#resolve_unhandled). ## Multi-Statement Client SQL Limitations It is important to understand that limitations on multi-statement client SQL has nothing to do with executing multiple SQL statements at once, or in one transaction. Of course, it is assumed that will often or even usually be the case, and this framework can handle that just fine. The complexities and limitations come when the *client* sends all SQL statements as one single string to Postgres. Assume the following SQL statements: ```sql CREATE TABLE foo (id serial primary key, bla text); INSERT INTO foo (bla) VALUES ('hello world'); ``` If this was in a file that I called via psql, it would run as two separate SQL command strings. However, if in python or ruby's ActiveRecord I create a single string as above and execute it, then it would be sent to Postgres as 1 single SQL command string. In such a case, this framework is aware that a multi-statement is being executed by using Postgres' parser to get the command tags of the full SQL statement. You have a little freedom here with the `allow_multi_statements` option: - If `false`, pgl_ddl_deploy will *only* auto-replicate a client SQL statement that contains 1 command tag that matches the event trigger command tag. That's really safe, but it means you may have a lot more unhandled deployments. - If `true`, pgl_ddl_deploy will *only* auto-replicate DDL that contains **safe** command tags to propagate. For example, mixed DDL and DML is forbidden, because executing such a statement would in effect double-execute DML on both provider and subscriber. However, if you have a `CREATE TABLE` and `ALTER TABLE` in one command, assuming the table should be included in replication based on your configuration, then such a statement will be sent to subscribers. But of course, we can't guarantee that all DDL statements in this command are on the same table - so there could be edge cases. In any case that a SQL statement cannot be automatically run on the subscriber based on these analyses, instead it will be logged as a `WARNING` and put into the `unhandled` table for manual processing. To resolve these, see [Resolving Unhandled DDL](#resolve_unhandled). The regression suite in the `sql` folder has examples of several of these cases. Thus, limitations on multi-statement SQL is largely based on how your client sends its messages in SQL to Postgres, and likewise how your developers tend to write SQL. The good thing about this is that it ought to be much easier to train developers to logically separate SQL in their migrations, as opposed to far more work we need to do when we don't have any kind of transparent DDL replication. These limitations obviously have to be weighed against the cost of not using a framework like this at all in your environment. The `unhandled` table and `WARNING` logs are designed to be leveraged with monitoring to create alerting around when manual intervention is required for DDL changes. ## Native Logical Supported Configurations The only known configuration limitation for native logical DDL replication is that only a publication that includes replicating inserts can support DDL replication. This relates to how the DDL queueing mechanism works. The default `CREATE PUBLICATION` in postgres includes publishing inserts. It can also be configured specifically with `WITH (publish = 'insert')`. For more details see https://www.postgresql.org/docs/current/sql-createpublication.html. # Resolving DDL Replication Issues ## Resolving Failed DDL on Subscribers In some cases, you may propagate DDL that fails on the subscriber, and replication will break, unless you have enabled `queue_subscriber_failures`. You will then need to: - Manually deploy with the same SQL statement modified so that it excludes the failing portion. For the example above of both adding a column and adding a foreign key, assuming we don't have that `unreplicated` table, then you would run: ```sql ALTER TABLE replicated.foo ADD COLUMN foo_id INT; ``` - Consume the change in affected replication slot using `pg_logical_slot_get_changes` **up to specific LSN** of the transaction which included the DDL statement to get replication working again (this is only human readable for pglogical, not native). - You could also "trick" the DDL into applying - for example, by creating a dummy object that will allow the DDL to apply even if you don't need it, then discarding those objects once replication is working again. - Re-enable replication for affected subscriber(s) If you are using `queue_subscriber_failures`, any DDL failures for your given configuration will be logged as failed in `pgl_ddl_deploy.subscriber_logs`. You can resolve issues, and attempt to re-deploy by using functions: ```sql --Retry all failed, in transactional order SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); --Retry only a single failed log SELECT pgl_ddl_deploy.retry_subscriber_log(subscriber_log_id); ``` When you retry subscriber logs, new rows are created for the retries, thus the rows with `succeeded = f` still remain. Queries like this are helpful: ```sql SELECT id, LEFT(ddl_sql, 30) AS sql, origin_subscriber_log_id, next_subscriber_log_id, succeeded FROM pgl_ddl_deploy.subscriber_logs WHERE NOT succeeded; ``` Then to check retry: ```sql WITH old AS ( SELECT id, LEFT(ddl_sql, 30) AS sql, origin_subscriber_log_id, next_subscriber_log_id, succeeded FROM pgl_ddl_deploy.subscriber_logs WHERE NOT succeeded) SELECT id, LEFT(ddl_sql, 30) AS sql, origin_subscriber_log_id, next_subscriber_log_id, succeeded FROM pgl_ddl_deploy.subscriber_logs WHERE id IN (SELECT next_subscriber_log_id FROM old); ``` ### No resolution: Last resort The following is only applicable to native replication. Sometimes there are cases where things are broken and you either can't figure out how to work around it, or it may in fact be impossible to process a particular set of operations within a transaction automatically. Although we would welcome a more proper feature to disable DDL replication on the subscriber, you can do so manually with the following command on the subscriber: ```sql ALTER TABLE pgl_ddl_deploy.queue DISABLE TRIGGER execute_queued_ddl; ``` **NOTE** that DDL replication will be disabled so long as you have done this, although you will see any changes come through as new rows in the `queue` table. BE SURE to re-enable the trigger as a `REPLICA TRIGGER` properly afterwards: ```sql ALTER TABLE pgl_ddl_deploy.queue ENABLE REPLICA TRIGGER execute_queued_ddl; ``` ## Resolving Unhandled DDL At present, an unhandled DDL deployment **may not break replication** by itself. If the DDL statement that could not be deployed doesn't actually affect any data being replicated (for example, a brand new table is added), then replication will continue. However, the situation should be resolved ASAP because it is assumed that whatever table(s) that were involved in the DDL **should** be propagated to subscribers. It is also possible that replication will break immediately. For example, if a column is added to the table, and data is replicating, it will fail immediately because of a column mismatch. In such cases you will need to: - Manually deploy the unhandled SQL statement modified so that it excludes the unhandled portion. For the example of a multi-statement SQL above, you would need to exclude the `INSERT` portion of the SQL and run: ```sql CREATE TABLE foo (id serial primary key, bla text); ``` - If a new table is involved, in this case you also will need to manually add the table to replication using `ALTER PUBLICATION ADD TABLE` or `pglogical.replication_set_add_table` depending on driver. - If a new table is involved, you may need to resynchronize the table if data has been replicating for it - If a new table is NOT involved (for example `ALTER TABLE ADD COLUMN`), then replication will simply continue where it broke - Re-enable replication for affected subscriber(s) - Mark your unhandled records as resolved using function: ```sql SELECT pgl_ddl_deploy.resolve_unhandled(unhandled_id INT, notes TEXT = NULL); ``` To be more conservative, we may want a feature that forces replication to break if there is an unhandled deployment, for example, by sending an exception through `replicate_ddl_command`. But such a feature may cause additional and unnecessary administration overhead, since it is likely the strictness of replication will cause the system to break when it should. ## Disable DDL Replication on Subscriber The following applies to native logical replication only. As a last resort, you might find some unexpected problem where you want to disable DDL replication on the subscriber because you are in an error loop. This should be done very cautiously and as a last resort. Disable the trigger on the DDL queue table to ignore all DDL replication on subscriber: ``` ALTER TABLE pgl_ddl_deploy.queue DISABLE TRIGGER execute_queued_ddl; ``` Do what you need to do for manual fixes. If you suspect a bug, please report it. When you are finished, you MUST ENABLE THE TRIGGER IN REPLICATION MODE ONLY: ``` ALTER TABLE pgl_ddl_deploy.queue ENABLE REPLICA TRIGGER execute_queued_ddl; ``` # For Developers ## Help Wanted Features We are currently using the parser only to get the list of command tags. One big advantage to this is that it isn't going to be difficult to maintain as Postgres develops. One disadvantage is that it tells us nothing about the actual objects being modified, nor the type of modification. We believe it is feasible to use the parser to actually only process the parts of a multi-statement SQL command that we want to, but this far more ambitious, and would be more work to maintain. We would need to leverage the different structures of each DDL command's `parsetree` to determine if the table is in a schema we care to replicate. Then, we would need to use the parser's lex code to take out the piece(s) we want. Theoretically speaking, don't we have all that we need in the SQL statement + the parser to programatically use only what we want and send it to subscribers? I think we do, but lots of work would be required, and we would welcome those with more comfort in the parser code to help if interested. ## SQL files To build the higher version SQL files (i.e. 2.0) from the lower versions + the upgrade patch SQL files, run `pgl_ddl_deploy-sql-maker.sh`. Note the script `correct_2.0_for_no_pglogical.py`. This was used to edit the long `.sql` files to safely remove the hard dependency on pglogical. This should not be needed long-term (i.e. once moving to versions above 2.0). ## Regression testing The building of the regression suite has changed since adding native logical support. There are several scripts involved to manage this. Basically, I am duplicating the entire original test suite and making in-place changes in order to create a separate test flow for native logical replication, but also supporting the other pg versions prior to pg10. Here is a brief explanation: 1. The script `duplicate_tests_for_native.py` copies all of the tests, adding conditionals where needed. It's not pretty but was the most efficient way of managing the test suite with these 2 test flows. It is not perfect, but once it has been run, the test failures can be seen to be only white space issues that then can be easily resolved by re-copying the `results` file to `expected`. It is desirable to have as few changes as possible between the two test suites. 2. The script `generate_new_native_tests.py` is to generate numerically a brand new test only applicable to native logical replication. You can run the regression suite, which must be on a server that has pglogical packages available, and a cluster that is configured to allow creating the pglogical extension (i.e. adding it to `shared_preload_libraries`). Note that the regression suite does not do any cross-server replication testing, but it does cover a wide variety of replication cases and the core of what is needed to verify DDL replication. As with any Postgres extension: ``` make install make installcheck # There is support for testing both 1.4 and the upgraded path from 1.4 to 1.5 FROMVERSION=1.4 make installcheck # Test from 1.4 upgrade to 1.5 FROMVERSION=1.5 make installcheck ``` pgl_ddl_deploy-2.2.1/correct_2.0_for_no_pglogical.py000077500000000000000000000012201453171545300224600ustar00rootroot00000000000000#!/usr/bin/env python3 from shutil import copyfile import os file = './pgl_ddl_deploy--2.0.sql' old = file new = f"{file}.new" delete_ranges = [ (62,146), (291,891), (975,1053), (1520,1587), (1746,1747), (1748,3009), (3012,3015), (3019,3655), (3673,3700), (3807,4563), (4681,4684), (4722,4723), (5138,5822), (5855,5865), (6008,6701), ] n = 0 with open(old) as oldfile, open(new, 'w') as newfile: for line in oldfile: n += 1 if any(lower <= n <= upper for (lower, upper) in delete_ranges): pass else: newfile.write(line) copyfile(new, old) pgl_ddl_deploy-2.2.1/ddl_deparse.c000066400000000000000000000177631453171545300171350ustar00rootroot00000000000000/*---------------------------------------------------------------------- * ddl_deparse.c * Taken directly from Postgres test_ddl_deparse test module. * *---------------------------------------------------------------------- */ #include "postgres.h" #include "catalog/pg_type.h" #include "tcop/deparse_utility.h" #include "tcop/utility.h" #include "utils/builtins.h" PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(get_command_type); PG_FUNCTION_INFO_V1(get_command_tag); PG_FUNCTION_INFO_V1(get_altertable_subcmdinfo); /* * Return the textual representation of the struct type used to represent a * command in struct CollectedCommand format. */ Datum get_command_type(PG_FUNCTION_ARGS) { CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0); const char *type; switch (cmd->type) { case SCT_Simple: type = "simple"; break; case SCT_AlterTable: type = "alter table"; break; case SCT_Grant: type = "grant"; break; case SCT_AlterOpFamily: type = "alter operator family"; break; case SCT_AlterDefaultPrivileges: type = "alter default privileges"; break; case SCT_CreateOpClass: type = "create operator class"; break; case SCT_AlterTSConfig: type = "alter text search configuration"; break; default: type = "unknown command type"; break; } PG_RETURN_TEXT_P(cstring_to_text(type)); } /* * Return the command tag corresponding to a parse node contained in a * CollectedCommand struct. */ Datum get_command_tag(PG_FUNCTION_ARGS) { CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0); if (!cmd->parsetree) PG_RETURN_NULL(); #if PG_VERSION_NUM >= 130000 PG_RETURN_TEXT_P(cstring_to_text(CreateCommandName(cmd->parsetree))); #else PG_RETURN_TEXT_P(cstring_to_text(CreateCommandTag(cmd->parsetree))); #endif } /* * Return a text array representation of the subcommands of an ALTER TABLE * command. */ Datum get_altertable_subcmdinfo(PG_FUNCTION_ARGS) { CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0); ArrayBuildState *astate = NULL; ListCell *cell; if (cmd->type != SCT_AlterTable) elog(ERROR, "command is not ALTER TABLE"); foreach(cell, cmd->d.alterTable.subcmds) { CollectedATSubcmd *sub = lfirst(cell); AlterTableCmd *subcmd = castNode(AlterTableCmd, sub->parsetree); const char *strtype; switch (subcmd->subtype) { #if PG_VERSION_NUM < 120000 case AT_AddOids: strtype = "ADD OIDS"; break; case AT_AddOidsRecurse: strtype = "ADD OIDS (and recurse)"; break; #endif #if PG_VERSION_NUM >= 120000 case AT_CheckNotNull: strtype = "CHECK NOT NULL"; break; #endif #if PG_VERSION_NUM >= 130000 case AT_CookedColumnDefault: strtype = "ALTER COLUMN SET DEFAULT (precooked)"; break; #endif #if PG_VERSION_NUM < 160000 case AT_AddColumnRecurse: strtype = "ADD COLUMN (and recurse)"; break; case AT_DropColumnRecurse: strtype = "DROP COLUMN (and recurse)"; break; case AT_AddConstraintRecurse: strtype = "ADD CONSTRAINT (and recurse)"; break; case AT_ValidateConstraintRecurse: strtype = "VALIDATE CONSTRAINT (and recurse)"; break; case AT_DropConstraintRecurse: strtype = "DROP CONSTRAINT (and recurse)"; break; #endif #if PG_VERSION_NUM >= 160000 case AT_DropExpression: strtype = "DROP EXPRESSION"; break; case AT_SetCompression: strtype = "SET COMPRESSION"; break; case AT_ReAddDomainConstraint: strtype = "(re) ADD DOMAIN CONSTRAINT"; break; case AT_SetAccessMethod: strtype = "SET ACCESS METHOD"; break; case AT_DetachPartition: strtype = "DETACH PARTITION"; break; case AT_AttachPartition: strtype = "ATTACH PARTITION"; break; case AT_DetachPartitionFinalize: strtype = "DETACH PARTITION ... FINALIZE"; break; case AT_AddIdentity: strtype = "ADD IDENTITY"; break; case AT_SetIdentity: strtype = "SET IDENTITY"; break; case AT_DropIdentity: strtype = "DROP IDENTITY"; break; case AT_ReAddStatistics: strtype = "(re) ADD STATS"; break; #endif case AT_AddColumn: strtype = "ADD COLUMN"; break; case AT_AddColumnToView: strtype = "ADD COLUMN TO VIEW"; break; case AT_ColumnDefault: strtype = "ALTER COLUMN SET DEFAULT"; break; case AT_DropNotNull: strtype = "DROP NOT NULL"; break; case AT_SetNotNull: strtype = "SET NOT NULL"; break; case AT_SetStatistics: strtype = "SET STATS"; break; case AT_SetOptions: strtype = "SET OPTIONS"; break; case AT_ResetOptions: strtype = "RESET OPTIONS"; break; case AT_SetStorage: strtype = "SET STORAGE"; break; case AT_DropColumn: strtype = "DROP COLUMN"; break; case AT_AddIndex: strtype = "ADD INDEX"; break; case AT_ReAddIndex: strtype = "(re) ADD INDEX"; break; case AT_AddConstraint: strtype = "ADD CONSTRAINT"; break; case AT_ReAddConstraint: strtype = "(re) ADD CONSTRAINT"; break; case AT_AlterConstraint: strtype = "ALTER CONSTRAINT"; break; case AT_ValidateConstraint: strtype = "VALIDATE CONSTRAINT"; break; case AT_AddIndexConstraint: strtype = "ADD CONSTRAINT (using index)"; break; case AT_DropConstraint: strtype = "DROP CONSTRAINT"; break; case AT_ReAddComment: strtype = "(re) ADD COMMENT"; break; case AT_AlterColumnType: strtype = "ALTER COLUMN SET TYPE"; break; case AT_AlterColumnGenericOptions: strtype = "ALTER COLUMN SET OPTIONS"; break; case AT_ChangeOwner: strtype = "CHANGE OWNER"; break; case AT_ClusterOn: strtype = "CLUSTER"; break; case AT_DropCluster: strtype = "DROP CLUSTER"; break; case AT_SetLogged: strtype = "SET LOGGED"; break; case AT_SetUnLogged: strtype = "SET UNLOGGED"; break; case AT_DropOids: strtype = "DROP OIDS"; break; case AT_SetTableSpace: strtype = "SET TABLESPACE"; break; case AT_SetRelOptions: strtype = "SET RELOPTIONS"; break; case AT_ResetRelOptions: strtype = "RESET RELOPTIONS"; break; case AT_ReplaceRelOptions: strtype = "REPLACE RELOPTIONS"; break; case AT_EnableTrig: strtype = "ENABLE TRIGGER"; break; case AT_EnableAlwaysTrig: strtype = "ENABLE TRIGGER (always)"; break; case AT_EnableReplicaTrig: strtype = "ENABLE TRIGGER (replica)"; break; case AT_DisableTrig: strtype = "DISABLE TRIGGER"; break; case AT_EnableTrigAll: strtype = "ENABLE TRIGGER (all)"; break; case AT_DisableTrigAll: strtype = "DISABLE TRIGGER (all)"; break; case AT_EnableTrigUser: strtype = "ENABLE TRIGGER (user)"; break; case AT_DisableTrigUser: strtype = "DISABLE TRIGGER (user)"; break; case AT_EnableRule: strtype = "ENABLE RULE"; break; case AT_EnableAlwaysRule: strtype = "ENABLE RULE (always)"; break; case AT_EnableReplicaRule: strtype = "ENABLE RULE (replica)"; break; case AT_DisableRule: strtype = "DISABLE RULE"; break; case AT_AddInherit: strtype = "ADD INHERIT"; break; case AT_DropInherit: strtype = "DROP INHERIT"; break; case AT_AddOf: strtype = "OF"; break; case AT_DropOf: strtype = "NOT OF"; break; case AT_ReplicaIdentity: strtype = "REPLICA IDENTITY"; break; case AT_EnableRowSecurity: strtype = "ENABLE ROW SECURITY"; break; case AT_DisableRowSecurity: strtype = "DISABLE ROW SECURITY"; break; case AT_ForceRowSecurity: strtype = "FORCE ROW SECURITY"; break; case AT_NoForceRowSecurity: strtype = "NO FORCE ROW SECURITY"; break; case AT_GenericOptions: strtype = "SET OPTIONS"; break; default: strtype = "unrecognized"; break; } astate = accumArrayResult(astate, CStringGetTextDatum(strtype), false, TEXTOID, CurrentMemoryContext); } if (astate == NULL) elog(ERROR, "empty alter table subcommand list"); PG_RETURN_ARRAYTYPE_P(DatumGetPointer(makeArrayResult(astate, CurrentMemoryContext))); } pgl_ddl_deploy-2.2.1/debian/000077500000000000000000000000001453171545300157275ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/debian/README.md000066400000000000000000000010031453171545300172000ustar00rootroot00000000000000# Debian/Ubuntu packaging This directory contains the Debian control section for pgl_ddl_deploy 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 ``` pgl_ddl_deploy-2.2.1/debian/changelog000066400000000000000000000072261453171545300176100ustar00rootroot00000000000000pgl-ddl-deploy (2.2.1-1) unstable; urgency=medium * Fix 2.2 upgrade scripts -- Jeremy Finzel Mon, 27 Nov 2023 12:39:52 -0600 pgl-ddl-deploy (2.2.0-1) unstable; urgency=medium * Support for Postgres 16 -- Jeremy Finzel Tue, 17 Oct 2023 14:57:39 -0500 pgl-ddl-deploy (2.1.0-2) unstable; urgency=medium * Team upload for PostgreSQL 14. * Use dh --with pgxs. * R³: no. * DH 13. * debian/tests: Use 'make' instead of postgresql-server-dev-all. -- Christoph Berg Mon, 25 Oct 2021 12:51:30 +0200 pgl-ddl-deploy (2.1.0-1) unstable; urgency=medium * Fix duplicate issue with multiple subscriptions -- Jeremy Finzel Fri, 19 Feb 2021 11:21:59 -0600 pgl-ddl-deploy (2.0.0-2) unstable; urgency=medium [ Jeremy Finzel ] * Regression fix - add -o wal_level=logical [ Christian Ehrhardt ] * d/changelog: fix new version to be >2.0.0-1 * revert unmentioned drop of needs-root in d/t/control * d/t/control: update test dependencies for postgresql-13 -- Christian Ehrhardt Mon, 07 Dec 2020 14:18:20 +0100 pgl-ddl-deploy (2.0.0-1) unstable; urgency=medium * Support for Native Logical Replication * Support for Postgres 13 (Closes: #972505) -- Jeremy Finzel Fri, 06 Nov 2020 12:32:30 -0600 pgl-ddl-deploy (1.7.0-2) UNRELEASED; urgency=low * Trim trailing whitespace. * Bump debhelper from deprecated 9 to 12. * Set debhelper-compat version in Build-Depends. * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository, Repository-Browse. * Update standards version to 4.2.1, no changes needed. -- Debian Janitor Thu, 04 Jun 2020 18:27:15 -0000 pgl-ddl-deploy (1.7.0-1) unstable; urgency=medium * Support for Postgres 12 * Support for pglogical 2.3.0 -- Michael Elterman Mon, 16 Mar 2020 15:37:28 -0500 pgl-ddl-deploy (1.6.0-1) unstable; urgency=critical (fixes compatibility with pglogical 2.2.2) * Fix incompatibility with pglogical 2.2.2 * Other various bug fixes -- Jeremy Finzel Fri, 30 Aug 2019 10:37:30 -0500 pgl-ddl-deploy (1.5.1-1) unstable; urgency=medium * Fix test race conditions and unstable output -- Jeremy Finzel Tue, 15 Jan 2019 11:04:16 -0600 pgl-ddl-deploy (1.5.0-1) unstable; urgency=medium * Support more commands and allow kill blocking subscriber processes -- Jeremy Finzel Wed, 26 Dec 2018 14:45:07 -0600 pgl-ddl-deploy (1.4.0-2) unstable; urgency=medium * Team upload. * debian/control(.in): Add Vcs-Browser, bump Standards-Version, and update for PostgreSQL 11. * debian/tests/control: Tests require user "postgres", so run as root. -- Christoph Berg Mon, 29 Oct 2018 12:37:19 +0100 pgl-ddl-deploy (1.4.0-1) unstable; urgency=medium * Support ddl-only replication and alter table subcommand filtering -- Jeremy Finzel Wed, 17 Oct 2018 09:56:24 -0500 pgl-ddl-deploy (1.3.0-1) unstable; urgency=medium * Version 1.3.0 from upstream. -- Dominic Salvador Tue, 17 Apr 2018 14:04:55 -0500 pgl-ddl-deploy (1.2.0-1) unstable; urgency=medium * Version 1.2.0 from upstream. -- Dominic Salvador Thu, 25 Jan 2018 13:41:54 -0600 pgl-ddl-deploy (1.1.0-1) unstable; urgency=medium * Version 1.1.0 from upstream. -- Dominic Salvador Tue, 02 Jan 2018 12:23:49 -0600 pgl-ddl-deploy (1.0.0-1) unstable; urgency=medium * Version 1.0.0 from upstream. -- Dominic Salvador Tue, 05 Sep 2017 13:23:40 -0500 pgl_ddl_deploy-2.2.1/debian/control000066400000000000000000000012001453171545300173230ustar00rootroot00000000000000Source: pgl-ddl-deploy Section: database Priority: optional Maintainer: Jeremy Finzel Build-Depends: debhelper-compat (= 13), libpq-dev, postgresql-common, postgresql-server-dev-all Standards-Version: 4.5.0 Rules-Requires-Root: no Homepage: https://github.com/enova/pgl_ddl_deploy Vcs-Browser: https://github.com/enova/pgl_ddl_deploy Vcs-Git: https://github.com/enova/pgl_ddl_deploy.git Package: postgresql-14-pgl-ddl-deploy Architecture: any Depends: postgresql-14, ${shlibs:Depends}, ${misc:Depends} Description: Transparent DDL replication for PostgreSQL Automated DDL deployment using PgLogical for PostgreSQL 14. pgl_ddl_deploy-2.2.1/debian/control.in000066400000000000000000000012251453171545300177370ustar00rootroot00000000000000Source: pgl-ddl-deploy Section: database Priority: optional Maintainer: Jeremy Finzel Build-Depends: debhelper-compat (= 13), libpq-dev, postgresql-common, postgresql-server-dev-all Standards-Version: 4.5.0 Rules-Requires-Root: no Homepage: https://github.com/enova/pgl_ddl_deploy Vcs-Browser: https://github.com/enova/pgl_ddl_deploy Vcs-Git: https://github.com/enova/pgl_ddl_deploy.git Package: postgresql-PGVERSION-pgl-ddl-deploy Architecture: any Depends: postgresql-PGVERSION, ${shlibs:Depends}, ${misc:Depends} Description: Transparent DDL replication for PostgreSQL Automated DDL deployment using PgLogical for PostgreSQL PGVERSION. pgl_ddl_deploy-2.2.1/debian/copyright000066400000000000000000000042141453171545300176630ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pgl-ddl-deploy Source: https://github.com/enova/pgl_ddl_deploy Files: * Copyright: 2017-2023 Enova International, Inc. 2017-2023 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: 2023 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". pgl_ddl_deploy-2.2.1/debian/docs000066400000000000000000000000121453171545300165730ustar00rootroot00000000000000README.md pgl_ddl_deploy-2.2.1/debian/pgversions000066400000000000000000000000041453171545300200430ustar00rootroot0000000000000011+ pgl_ddl_deploy-2.2.1/debian/rules000077500000000000000000000002141453171545300170040ustar00rootroot00000000000000#!/usr/bin/make -f override_dh_pgxs_test: # defer testing to autopkgtest, we need postgresql-*-pglogical installed %: dh $@ --with pgxs pgl_ddl_deploy-2.2.1/debian/source/000077500000000000000000000000001453171545300172275ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/debian/source/format000066400000000000000000000000141453171545300204350ustar00rootroot000000000000003.0 (quilt) pgl_ddl_deploy-2.2.1/debian/tests/000077500000000000000000000000001453171545300170715ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/debian/tests/control000066400000000000000000000002471453171545300204770ustar00rootroot00000000000000# requires user "postgres", so run as root Depends: @, make, postgresql-contrib-14, postgresql-14-pglogical Tests: installcheck Restrictions: allow-stderr, needs-root pgl_ddl_deploy-2.2.1/debian/tests/control.in000066400000000000000000000002651453171545300211040ustar00rootroot00000000000000# requires user "postgres", so run as root Depends: @, make, postgresql-contrib-PGVERSION, postgresql-PGVERSION-pglogical Tests: installcheck Restrictions: allow-stderr, needs-root pgl_ddl_deploy-2.2.1/debian/tests/installcheck000077500000000000000000000001361453171545300214630ustar00rootroot00000000000000#!/bin/sh pg_buildext -o shared_preload_libraries=pglogical -o wal_level=logical installcheck pgl_ddl_deploy-2.2.1/debian/upstream/000077500000000000000000000000001453171545300175675ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/debian/upstream/metadata000066400000000000000000000003631453171545300212740ustar00rootroot00000000000000--- Bug-Database: https://github.com/enova/pgl_ddl_deploy/issues Bug-Submit: https://github.com/enova/pgl_ddl_deploy/issues/new Repository: https://github.com/enova/pgl_ddl_deploy.git Repository-Browse: https://github.com/enova/pgl_ddl_deploy pgl_ddl_deploy-2.2.1/debian/watch000066400000000000000000000001131453171545300167530ustar00rootroot00000000000000version=4 https://github.com/enova/pgl_ddl_deploy/releases .*/v(.*).tar.gz pgl_ddl_deploy-2.2.1/duplicate_tests_for_native.py000077500000000000000000000153361453171545300225020ustar00rootroot00000000000000#!/usr/bin/env python3 from shutil import copyfile import os last_original_test = 28 sql = './sql' expected = './expected' TO_MODIFY = ['create_ext', 'setup', 'deploy_update', 'new_set_behavior', '1_4_features', 'sub_retries', 'new_setup', 'multi_set_tags'] IF_NATIVE_START = """DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ """ IF_NATIVE_END = """END IF; END$$; """ MAKE_SET_DRIVER_FUNC = f"""CREATE FUNCTION set_driver() RETURNS VOID AS $BODY$ BEGIN IF current_setting('server_version_num')::INT >= 100000 AND (SELECT extversion::numeric FROM pg_extension WHERE extname = 'pgl_ddl_deploy') >= 2.0 THEN ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN driver SET DEFAULT 'native'::pgl_ddl_deploy.driver; UPDATE pgl_ddl_deploy.set_configs SET driver = 'native'::pgl_ddl_deploy.driver; END IF; END; $BODY$ LANGUAGE plpgsql; """ SET_DRIVER = 'SELECT set_driver();\n' files = {} for filename in os.listdir(sql): split_filename = filename.split("_", 1) number = int(split_filename[0]) if number > last_original_test: next else: files[int(split_filename[0])] = split_filename[1] def construct_filename(n, name): return f"{str(n).zfill(2)}_{name}" def handle_rep_config(old, new, line_start, line_end, native_statements_to_add, output_offset_start=0, output_offset_end=0): n = 0 if old.endswith('.out'): line_start = line_start + output_offset_start line_end = line_end + output_offset_end with open(old) as oldfile, open(new, 'w') as newfile: for line in oldfile: n += 1 if n == line_start: newfile.write(IF_NATIVE_START) newfile.write("\n".join(native_statements_to_add)) newfile.write('$sql$;\nELSE\n') newfile.write(line) elif n == line_end: newfile.write(IF_NATIVE_END) newfile.write(line) else: newfile.write(line) def validate(name): if not name in TO_MODIFY: raise ValueError(f"name {name} is not in the list of modified files: {to_modify}") def make_native_file(old, new): name = old.split("/")[2].split(".")[0].split("_", 1)[1] to_modify = ['create_ext', 'setup', 'deploy_update', 'new_set_behavior', '1_4_features', 'sub_retries', 'new_setup'] if name == 'create_ext': validate(name) removes = ['CREATE EXTENSION pglogical'] with open(old) as oldfile, open(new, 'w') as newfile: for line in oldfile: if not any(remove in line for remove in removes): newfile.write(line) else: newfile.write("""CREATE TEMP TABLE v AS SELECT :'v'::TEXT AS num; DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 AND (SELECT num FROM v) != ALL('{1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7}'::text[]) THEN\n""") newfile.write("RAISE LOG '%', 'USING NATIVE';\n") newfile.write('\nELSE\n') newfile.write(line) newfile.write(IF_NATIVE_END) newfile.write("DROP TABLE v;\n") with open(new, 'a') as newfile: newfile.write(MAKE_SET_DRIVER_FUNC) newfile.write(SET_DRIVER) elif name == 'setup': validate(name) pubname_prefix = 'test' statements = [] for i in range(1, 9): statements.append(f"CREATE PUBLICATION {pubname_prefix}{i};") handle_rep_config(old, new, 1, 19, statements, 0, -3) elif name == 'deploy_update': validate(name) newtmp = f"{new}.tmp" with open(old) as oldfile, open(newtmp, 'w') as newfile: for line in oldfile: if "ALTER EXTENSION" in line: newfile.write(line) newfile.write(SET_DRIVER) else: newfile.write(line) pubname = 'testtemp' handle_rep_config(newtmp, new, 25, 34, [f"CREATE PUBLICATION {pubname};"], 3, 2) elif name == 'new_set_behavior': validate(name) handle_rep_config(old, new, 18, 37, ["CREATE PUBLICATION my_special_tables_1;", "CREATE PUBLICATION my_special_tables_2;"], -2, -4) elif name == '1_4_features': validate(name) handle_rep_config(old, new, 11, 29, ["CREATE PUBLICATION test_ddl_only;"], 11, 9) elif name == 'sub_retries': validate(name) with open(old) as oldfile, open(new, 'w') as newfile: for line in oldfile: if "CREATE EXTENSION" in line: newfile.write(line) newfile.write(SET_DRIVER) else: newfile.write(line) elif name == 'new_setup': validate(name) n = 0 line_start = 22 line_end = 40 output_offset_start = -6 output_offset_end = -8 if old.endswith('.out'): line_start = line_start + output_offset_start line_end = line_end + output_offset_end with open(old) as oldfile, open(new, 'w') as newfile: for line in oldfile: n += 1 if "CREATE EXTENSION" in line: newfile.write(line) newfile.write(SET_DRIVER) elif n == line_start: newfile.write(IF_NATIVE_START) newfile.write(f"CREATE PUBLICATION testspecial;\n") newfile.write('$sql$;\nELSE\n') newfile.write(line) elif n == line_end: newfile.write(IF_NATIVE_END) newfile.write(line) else: newfile.write(line) elif name == 'multi_set_tags': validate(name) copyfile(old, new) with open(new, 'a') as newfile: newfile.write(""" DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN SELECT COUNT(1) INTO v_ct FROM pg_publication_tables WHERE schemaname = 'pgl_ddl_deploy' AND tablename = 'queue'; RAISE LOG 'v_ct: %', v_ct; IF v_ct != 8 THEN RAISE EXCEPTION 'Count does not match expected: v_ct: %', v_ct; END IF; END IF; END$$;""") else: copyfile(old, new) new_test_names = [] for n, name in files.items(): orig = construct_filename(n, name) new = construct_filename(n + last_original_test, name) make_native_file(f"{sql}/{orig}", f"{sql}/{new}") make_native_file(f"{expected}/{orig.replace('.sql','.out')}", f"{expected}/{new.replace('.sql','.out')}") new_test_names.append(new.replace('.sql', '')) final = [f"\n {test_name} \\" for test_name in new_test_names] print("FILES MODIFIED:") print("\n".join(TO_MODIFY)) print("".join(sorted(final))) pgl_ddl_deploy-2.2.1/expected/000077500000000000000000000000001453171545300163065ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/expected/01_create_ext.out000066400000000000000000000003041453171545300214570ustar00rootroot00000000000000-- Allow running regression suite with upgrade paths \set v `echo ${FROMVERSION:-2.2}` SET client_min_messages = warning; CREATE EXTENSION pglogical; CREATE EXTENSION pgl_ddl_deploy VERSION :'v'; pgl_ddl_deploy-2.2.1/expected/02_setup.out000066400000000000000000000051041453171545300205000ustar00rootroot00000000000000CREATE TEMP TABLE foonode AS SELECT pglogical.create_node('test','host=localhost'); DROP TABLE foonode; CREATE TEMP TABLE repsets AS 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 FROM sets s; DROP TABLE repsets; CREATE ROLE test_pgl_ddl_deploy LOGIN; GRANT CREATE ON DATABASE contrib_regression TO test_pgl_ddl_deploy; GRANT CREATE ON SCHEMA public TO PUBLIC; SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname = 'test_pgl_ddl_deploy'; add_role ---------- t (1 row) SET ROLE test_pgl_ddl_deploy; CREATE FUNCTION check_rep_tables() RETURNS TABLE (set_name TEXT, table_name TEXT) AS $BODY$ BEGIN -- Handle change from view to function rep_set_table_wrapper IF (SELECT extversion FROM pg_extension WHERE extname = 'pgl_ddl_deploy') = ANY('{1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7}'::text[]) THEN RETURN QUERY EXECUTE $$ SELECT set_name::TEXT, set_reloid::TEXT AS table_name FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) ORDER BY set_name::TEXT, set_reloid::TEXT;$$; ELSE RETURN QUERY EXECUTE $$ SELECT name::TEXT AS set_name, relid::regclass::TEXT AS table_name FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE relid::regclass::TEXT <> 'pgl_ddl_deploy.queue' ORDER BY name::TEXT, relid::TEXT;$$; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION all_queues() RETURNS TABLE (queued_at timestamp with time zone, role name, pubnames text[], message_type "char", -- we use json here to provide test output consistency whether native or pglogical message json) AS $BODY$ BEGIN IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY EXECUTE $$ SELECT queued_at, role, replication_sets AS pubnames, message_type, message FROM pglogical.queue UNION ALL SELECT queued_at, role, pubnames, message_type, to_json(message) AS message FROM pgl_ddl_deploy.queue;$$; ELSE RETURN QUERY EXECUTE $$ SELECT queued_at, role, pubnames, message_type, to_json(message) AS message FROM pgl_ddl_deploy.queue; $$; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION verify_count(ct int, expected int) RETURNS BOOLEAN AS $BODY$ BEGIN RAISE LOG 'ct: %', ct; IF ct != expected THEN RAISE EXCEPTION 'Count % does not match expected count of %', ct, expected; END IF; RETURN TRUE; END$BODY$ LANGUAGE plpgsql; pgl_ddl_deploy-2.2.1/expected/03_add_configs.out000066400000000000000000000027251453171545300216070ustar00rootroot00000000000000INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test1','.*',true, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test2','.*',true, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test3','.*',false, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test4','.*',false, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test5','^foo.*',true, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test6','^foo.*',true, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test7','^foo.*',false, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test8','^foo.*',false, false); --Ensure regex must be valid INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test9','^foo.*((',false, false); ERROR: invalid regular expression: parentheses () not balanced pgl_ddl_deploy-2.2.1/expected/04_deploy.out000066400000000000000000000022731453171545300206420ustar00rootroot00000000000000--These will show different warnings depending on version SET client_min_messages = error; \set VERBOSITY TERSE /*** No deploy allowed if table would be added to replication ***/ SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key); SET ROLE postgres; SELECT pgl_ddl_deploy.deploy('test1'); deploy -------- f (1 row) SET ROLE test_pgl_ddl_deploy; DROP TABLE foo; SET ROLE postgres; --This should work now SELECT pgl_ddl_deploy.deploy('test1'); deploy -------- t (1 row) --This should work SELECT pgl_ddl_deploy.disable('test1'); disable --------- t (1 row) --This should not work SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key); SET ROLE postgres; SELECT pgl_ddl_deploy.enable('test1'); enable -------- f (1 row) SET ROLE test_pgl_ddl_deploy; DROP TABLE foo; SET ROLE postgres; --This should work now SELECT pgl_ddl_deploy.enable('test1'); enable -------- t (1 row) --Enable all the rest DO $$ DECLARE v_rec RECORD; BEGIN FOR v_rec IN SELECT DISTINCT set_name FROM pgl_ddl_deploy.set_configs WHERE set_name LIKE 'test%' AND set_name <> 'test1' ORDER BY set_name LOOP PERFORM pgl_ddl_deploy.deploy(v_rec.set_name); END LOOP; END$$; pgl_ddl_deploy-2.2.1/expected/04_deploy_update.out000066400000000000000000000040331453171545300222000ustar00rootroot00000000000000--This will show different warnings depending on if we are actually updating to new version or not SET client_min_messages = error; ALTER EXTENSION pgl_ddl_deploy UPDATE; SELECT pgl_ddl_deploy.deploy('test1'); deploy -------- t (1 row) DO $$ DECLARE v_rec RECORD; BEGIN FOR v_rec IN SELECT name FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name LIKE 'test%' AND name <> 'test1' ORDER BY name LOOP PERFORM pgl_ddl_deploy.deploy(v_rec.name); END LOOP; END$$; --Now that we are on highest version, ensure WARNING shows CREATE TEMP TABLE repset AS SELECT pglogical.create_replication_set (set_name:='testtemp' ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE); DROP TABLE repset; SET client_min_messages = warning; BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (id, set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES (999, 'testtemp','.*',true, true); CREATE TABLE break(id serial primary key); SELECT pgl_ddl_deploy.deploy('testtemp'); WARNING: Deployment of auto-replication for id 999 set_name testtemp failed because 1 tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '.*' AND n.nspname !~* '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = 'testtemp' AND rsr.relid = c.oid AND rsr.driver = (SELECT driver FROM pgl_ddl_deploy.set_configs WHERE set_name = 'testtemp')); deploy -------- f (1 row) ROLLBACK; pgl_ddl_deploy-2.2.1/expected/05_allowed.out000066400000000000000000001253571453171545300210070ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; SET client_min_messages TO warning; /*** In default schema **/ CREATE TABLE foo(id serial primary key); SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ test1 | foo test2 | foo test3 | foo test4 | foo (4 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test2 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test1 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); (4 rows) ALTER TABLE foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test2 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test1 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); (8 rows) INSERT INTO foo (bla) VALUES (1),(2),(3); DROP TABLE foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); (10 rows) CREATE FUNCTION foo() RETURNS INT AS $BODY$ SELECT 1; $BODY$ LANGUAGE SQL STABLE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+--------------------------------------+-------------------------------------- test4 | CREATE FUNCTION foo() RETURNS INT AS+| CREATE FUNCTION foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test3 | CREATE FUNCTION foo() RETURNS INT AS+| CREATE FUNCTION foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test2 | CREATE FUNCTION foo() RETURNS INT AS+| CREATE FUNCTION foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test1 | CREATE FUNCTION foo() RETURNS INT AS+| CREATE FUNCTION foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; (10 rows) ALTER FUNCTION foo() OWNER TO current_user; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test4 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test3 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test2 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test1 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test4 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test3 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test2 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test1 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; (10 rows) DROP FUNCTION foo(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test4 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test3 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test2 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test1 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test4 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test3 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test2 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test1 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test4 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test3 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; (10 rows) CREATE VIEW fooview AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test4 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test3 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test2 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test1 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test4 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test3 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test2 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test1 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test4 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test3 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; (10 rows) ALTER VIEW fooview RENAME TO barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------+--------------------------------------- test4 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test3 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test2 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test1 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test4 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test3 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test2 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test1 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test4 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test3 | DROP FUNCTION foo(); | DROP FUNCTION foo(); (10 rows) DROP VIEW barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------+--------------------------------------- test4 | DROP VIEW barview; | DROP VIEW barview; test3 | DROP VIEW barview; | DROP VIEW barview; test2 | DROP VIEW barview; | DROP VIEW barview; test1 | DROP VIEW barview; | DROP VIEW barview; test4 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test3 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test2 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test1 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test4 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test3 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; (10 rows) CREATE SEQUENCE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------+--------------------------------------- test4 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test3 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test2 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test1 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test4 | DROP VIEW barview; | DROP VIEW barview; test3 | DROP VIEW barview; | DROP VIEW barview; test2 | DROP VIEW barview; | DROP VIEW barview; test1 | DROP VIEW barview; | DROP VIEW barview; test4 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test3 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; (10 rows) ALTER SEQUENCE foo RESTART; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test4 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test3 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test2 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test1 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test4 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test3 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test2 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test1 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test4 | DROP VIEW barview; | DROP VIEW barview; test3 | DROP VIEW barview; | DROP VIEW barview; (10 rows) DROP SEQUENCE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test4 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test3 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test2 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test1 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test4 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test3 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test2 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test1 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test4 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test3 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; (10 rows) CREATE SCHEMA foobar; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------+----------------------- test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test4 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test3 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test2 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test1 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test4 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test3 | DROP SEQUENCE foo; | DROP SEQUENCE foo; (10 rows) CREATE TABLE foobar.foo(id serial primary key); SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ test1 | foobar.foo test2 | foobar.foo test3 | foobar.foo test4 | foobar.foo test5 | foobar.foo test6 | foobar.foo test7 | foobar.foo test8 | foobar.foo (8 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------+------------------------------------------------- test8 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test7 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test6 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test5 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test4 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test3 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test2 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test1 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; (10 rows) ALTER TABLE foobar.foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------+------------------------------------------------- test8 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test7 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test6 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test5 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test4 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test8 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test7 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); (10 rows) INSERT INTO foobar.foo (bla) VALUES (1),(2),(3); DROP TABLE foobar.foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test2 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test1 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test8 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test7 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; (10 rows) CREATE FUNCTION foobar.foo() RETURNS INT AS $BODY$ SELECT 1; $BODY$ LANGUAGE SQL STABLE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test8 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test7 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test6 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test5 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test4 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test3 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test2 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test1 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) ALTER FUNCTION foobar.foo() OWNER TO current_user; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------------+---------------------------------------------------- test8 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test7 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test6 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test5 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test4 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test3 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test2 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test1 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test8 | CREATE FUNCTION foobar.foo() RETURNS INT AS +| CREATE FUNCTION foobar.foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test7 | CREATE FUNCTION foobar.foo() RETURNS INT AS +| CREATE FUNCTION foobar.foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; (10 rows) DROP FUNCTION foobar.foo(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------------+---------------------------------------------------- test8 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test7 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test6 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test5 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test4 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test3 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test2 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test1 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test8 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test7 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; (10 rows) CREATE VIEW foobar.fooview AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------+------------------------------- test8 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test7 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test6 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test5 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test4 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test3 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test2 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test1 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test8 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test7 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); (10 rows) ALTER VIEW foobar.fooview RENAME TO barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test8 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test7 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test6 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test5 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test4 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test3 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test2 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test1 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test8 | CREATE VIEW foobar.fooview AS +| CREATE VIEW foobar.fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test7 | CREATE VIEW foobar.fooview AS +| CREATE VIEW foobar.fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; (10 rows) DROP VIEW foobar.barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test8 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test7 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test6 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test5 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test4 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test3 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test2 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test1 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test8 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test7 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; (10 rows) CREATE SEQUENCE foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test8 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test7 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test6 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test5 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test4 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test3 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test2 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test1 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test8 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test7 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; (10 rows) ALTER SEQUENCE foobar.foo RESTART; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------+------------------------------------ test8 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test7 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test6 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test5 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test4 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test3 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test2 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test1 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test8 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test7 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; (10 rows) DROP SEQUENCE foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------+------------------------------------ test8 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test7 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test6 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test5 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test4 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test3 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test2 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test1 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test8 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test7 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; (10 rows) DROP SCHEMA foobar CASCADE; SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ (0 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test8 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test7 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test6 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test5 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test4 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test3 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test2 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test1 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test8 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test7 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; (10 rows) SELECT * FROM pgl_ddl_deploy.unhandled; id | set_name | pid | executed_at | ddl_sql_raw | command_tag | reason | txid | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+-------------+-------------+--------+------+---------------+----------+---------------- (0 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/06_multi.out000066400000000000000000000476711453171545300205150ustar00rootroot00000000000000SET log_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA foobar; --This should never be allowed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test4 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test3 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test2 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test1 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test8 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test7 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+---------------------------------------------------------------------------------------------------------------------+--------------+----------------------- test8 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test7 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test6 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test5 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test4 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test3 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test2 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test1 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test4 | CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo; | CREATE TABLE | rejected_command_tags test3 | CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo; | CREATE TABLE | rejected_command_tags (10 rows) --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foo(id int primary key); COMMIT;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled COMMIT \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled COMMIT SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------------------+------------------------------------------------ test7 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test5 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test3 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test1 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test3 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE foo(id int primary key); test1 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE foo(id int primary key); test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+---------------------------------------------------------------------------------------------------------------------+--------------+-------------------------- test8 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test6 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test8 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test7 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test6 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test5 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags (10 rows) --Run all commands through cli to avoid permissions issues \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo CASCADE;" DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foobar.foo CASCADE;" DROP TABLE --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------------------------------------------------------+----------------------------------------------------------------------------- test7 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test5 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test3 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test1 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test3 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; test1 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+--------------+-------------------------- test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test8 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test6 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement (10 rows) \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key);" CREATE TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key);" CREATE TABLE --This should be allowed by some but not others \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo, foobar.foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test4 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test3 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test2 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test1 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test8 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test7 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test6 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test5 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test4 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test3 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+--------------+-------------------------- test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) --Resolutions SELECT pgl_ddl_deploy.resolve_unhandled(id, 'DBA superhero deployed it manually on the subscribers!') FROM pgl_ddl_deploy.unhandled; resolve_unhandled ------------------- t t t t t t t t t t t t t t t t t t t t t t t t t t t t (28 rows) --Test with no rows and a dummy row SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; resolve_exception ------------------- (0 rows) BEGIN; INSERT INTO pgl_ddl_deploy.exceptions (set_name) VALUES ('test1'); SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; resolve_exception ------------------- t (1 row) ROLLBACK; SELECT resolved, resolved_notes, set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; resolved | resolved_notes | set_name | ddl_sql_raw | command_tag | reason ----------+--------------------------------------------------------+----------+-----------------------------------------------------------------------------+--------------+-------------------------- t | DBA superhero deployed it manually on the subscribers! | test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/06_multi_1.out000066400000000000000000000500511453171545300207170ustar00rootroot00000000000000SET log_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA foobar; --This should never be allowed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled CREATE TABLE INSERT 0 3 DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled CREATE TABLE INSERT 0 3 DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test4 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test3 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test2 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test1 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test8 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test7 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+---------------------------------------------------------------------------------------------------------------------+--------------+----------------------- test8 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test7 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test6 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test5 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test4 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test3 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test2 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test1 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test4 | CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo; | CREATE TABLE | rejected_command_tags test3 | CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo; | CREATE TABLE | rejected_command_tags (10 rows) --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foo(id int primary key); COMMIT;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled BEGIN CREATE TABLE COMMIT \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled BEGIN CREATE TABLE COMMIT SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------------------+------------------------------------------------ test7 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test5 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test3 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test1 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test3 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE foo(id int primary key); test1 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE foo(id int primary key); test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+---------------------------------------------------------------------------------------------------------------------+--------------+-------------------------- test8 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test6 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test8 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test7 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test6 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test5 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags (10 rows) --Run all commands through cli to avoid permissions issues \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo CASCADE;" DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foobar.foo CASCADE;" DROP TABLE --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled CREATE TABLE DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled CREATE TABLE DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------------------------------------------------------+----------------------------------------------------------------------------- test7 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test5 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test3 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test1 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test3 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; test1 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+--------------+-------------------------- test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test8 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test6 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement (10 rows) \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key);" CREATE TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key);" CREATE TABLE --This should be allowed by some but not others \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo, foobar.foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test4 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test3 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test2 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test1 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test8 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test7 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test6 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test5 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test4 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test3 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+--------------+-------------------------- test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) --Resolutions SELECT pgl_ddl_deploy.resolve_unhandled(id, 'DBA superhero deployed it manually on the subscribers!') FROM pgl_ddl_deploy.unhandled; resolve_unhandled ------------------- t t t t t t t t t t t t t t t t t t t t t t t t t t t t (28 rows) --Test with no rows and a dummy row SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; resolve_exception ------------------- (0 rows) BEGIN; INSERT INTO pgl_ddl_deploy.exceptions (set_name) VALUES ('test1'); SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; resolve_exception ------------------- t (1 row) ROLLBACK; SELECT resolved, resolved_notes, set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; resolved | resolved_notes | set_name | ddl_sql_raw | command_tag | reason ----------+--------------------------------------------------------+----------+-----------------------------------------------------------------------------+--------------+-------------------------- t | DBA superhero deployed it manually on the subscribers! | test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/07_edges.out000066400000000000000000000041571453171545300204430ustar00rootroot00000000000000SET client_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY); CREATE TABLE foo (id SERIAL PRIMARY KEY); --This is an edge case that currently can't be dealt with well with filtered replication. ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------------------------------+------------------------------------------------------------------ test8 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test7 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test6 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test5 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test4 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test3 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test2 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test1 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test4 | CREATE TABLE foo (id SERIAL PRIMARY KEY); | CREATE TABLE foo (id SERIAL PRIMARY KEY); test3 | CREATE TABLE foo (id SERIAL PRIMARY KEY); | CREATE TABLE foo (id SERIAL PRIMARY KEY); (10 rows) DROP TABLE foobar.foo CASCADE; DROP TABLE foo CASCADE; pgl_ddl_deploy-2.2.1/expected/08_ignored.out000066400000000000000000000023241453171545300207760ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; CREATE TEMP TABLE foo(id SERIAL PRIMARY KEY); ALTER TABLE foo ADD COLUMN bla TEXT; DROP TABLE foo; SELECT 1 AS myfield INTO TEMP foo; DROP TABLE foo; CREATE TEMP TABLE foo AS SELECT 1 AS myfield; DROP TABLE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+--------------------------------+-------------------------------- test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) pgl_ddl_deploy-2.2.1/expected/09_unsupported.out000066400000000000000000000150231453171545300217400ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo AS SELECT 1 AS myfield; WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+--------------------------------+-------------------------------- test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+-----------------+-------------------------- test4 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test3 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test2 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test1 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) SELECT 1 AS myfield INTO foobar.foo; WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+--------------------------------+-------------------------------- test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+--------------------------------------+-----------------+--------------------- test8 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test7 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test6 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test5 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test4 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test3 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test2 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test1 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test4 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test3 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | (10 rows) DROP TABLE foo; DROP TABLE foobar.foo; SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/10_no_create_user.out000066400000000000000000000003211453171545300223300ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE CREATE ROLE test_pgl_ddl_deploy_nopriv; SET ROLE test_pgl_ddl_deploy_nopriv; CREATE TEMP TABLE bla (id serial primary key); DROP TABLE bla; RESET ROLE; pgl_ddl_deploy-2.2.1/expected/11_override.out000066400000000000000000000020201453171545300211510ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE SET SESSION_REPLICATION_ROLE TO REPLICA; CREATE TABLE i_want_to_ignore_evts (id serial primary key); DROP TABLE i_want_to_ignore_evts; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------+------------------------ test8 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test7 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test6 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test5 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test4 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test3 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test2 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test1 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test4 | DROP TABLE foo; | DROP TABLE foo; test3 | DROP TABLE foo; | DROP TABLE foo; (10 rows) RESET SESSION_REPLICATION_ROLE; pgl_ddl_deploy-2.2.1/expected/12_sql_command_tags.out000066400000000000000000000011611453171545300226530ustar00rootroot00000000000000SELECT pgl_ddl_deploy.sql_command_tags(NULL); sql_command_tags ------------------ (1 row) SELECT pgl_ddl_deploy.sql_command_tags(''); ERROR: Invalid sql command SELECT pgl_ddl_deploy.sql_command_tags('CREATE EXTENSON foo;'); ERROR: syntax error at or near "EXTENSON" LINE 1: SELECT pgl_ddl_deploy.sql_command_tags('CREATE EXTENSON foo;... ^ SELECT pgl_ddl_deploy.sql_command_tags('CREATE TABLE foo(); ALTER TABLE foo ADD COLUMN bar text; DROP TABLE foo;'); sql_command_tags --------------------------------------------- {"CREATE TABLE","ALTER TABLE","DROP TABLE"} (1 row) pgl_ddl_deploy-2.2.1/expected/13_transaction.out000066400000000000000000000265111453171545300216740ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; SET client_min_messages TO warning; BEGIN; /*** In default schema **/ CREATE TABLE foo(id serial primary key); SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ test1 | foo test2 | foo test3 | foo test4 | foo (4 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test2 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test1 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test8 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test7 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test6 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test5 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test4 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test3 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; (10 rows) ALTER TABLE foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test2 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test1 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test8 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test7 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; (10 rows) INSERT INTO foo (bla) VALUES (1),(2),(3); DROP TABLE foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); (10 rows) CREATE TABLE foobar.foo(id serial primary key); SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ test1 | foobar.foo test2 | foobar.foo test3 | foobar.foo test4 | foobar.foo test5 | foobar.foo test6 | foobar.foo test7 | foobar.foo test8 | foobar.foo (8 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------+------------------------------------------------- test8 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test7 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test6 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test5 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test4 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test3 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test2 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test1 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; (10 rows) ALTER TABLE foobar.foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------+------------------------------------------------- test8 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test7 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test6 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test5 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test4 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test8 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test7 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); (10 rows) INSERT INTO foobar.foo (bla) VALUES (1),(2),(3); DROP TABLE foobar.foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test2 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test1 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test8 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test7 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; (10 rows) COMMIT; SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/15_new_set_behavior.out000066400000000000000000000120651453171545300226730ustar00rootroot00000000000000SET client_min_messages = warning; \set VERBOSITY terse --This should fail due to overlapping tags INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'test1', '.*', TRUE, TRUE, FALSE, '{"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"}', '{"DROP VIEW","DROP FUNCTION"}'; ERROR: You have overlapping configuration types and command tags which is not permitted: test1: include_schema_regex: ALTER FUNCTION, ALTER VIEW, CREATE FUNCTION, CREATE VIEW, DROP FUNCTION, DROP VIEW --But if we drop these tags from test1, it should work UPDATE pgl_ddl_deploy.set_configs SET create_tags = '{ALTER TABLE,CREATE SEQUENCE,ALTER SEQUENCE,CREATE SCHEMA,CREATE TABLE,CREATE TYPE,ALTER TYPE}', drop_tags = '{DROP SCHEMA,DROP TABLE,DROP TYPE,DROP SEQUENCE}' WHERE set_name = 'test1'; --Now this set will only handle these tags INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'test1', '.*', TRUE, TRUE, FALSE, '{"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"}', '{"DROP VIEW","DROP FUNCTION"}'; --include_only_repset_tables CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('my_special_tables_1'::TEXT), ('my_special_tables_2'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; --Only ALTER TABLE makes sense (and is allowed) with include_only_repset_tables. So this should fail INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"CREATE TABLE"}'; ERROR: new row for relation "set_configs" violates check constraint "repset_tables_restricted_tags" --This is OK INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'temp_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; DELETE FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; --This also should fail - no DROP tags at all allowed INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', '{"DROP TABLE"}'; ERROR: new row for relation "set_configs" violates check constraint "repset_tables_restricted_tags" --These both are OK INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_2', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; --Check we get the defaults we want from the trigger BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex) VALUES ('temp_1', '.*'); SELECT create_tags, drop_tags FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; create_tags | drop_tags -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------- {"ALTER TABLE","CREATE SEQUENCE","ALTER SEQUENCE","CREATE SCHEMA","CREATE TABLE","CREATE FUNCTION","ALTER FUNCTION","CREATE TYPE","ALTER TYPE","CREATE VIEW","ALTER VIEW",COMMENT,"CREATE RULE","CREATE TRIGGER","ALTER TRIGGER"} | {"DROP SCHEMA","DROP TABLE","DROP FUNCTION","DROP TYPE","DROP VIEW","DROP SEQUENCE"} (1 row) ROLLBACK; BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_only_repset_tables) VALUES ('temp_1', TRUE); SELECT create_tags, drop_tags FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; create_tags | drop_tags -----------------+----------- {"ALTER TABLE"} | (1 row) ROLLBACK; --Now deploy again separately --By set_name: SELECT pgl_ddl_deploy.deploy('test1'); deploy -------- t (1 row) --By set_config_id SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; deploy -------- t t (2 rows) pgl_ddl_deploy-2.2.1/expected/16_multi_set_tags.out000066400000000000000000000065601453171545300223770ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA viewer; --Should be handled by separate set_config CREATE TABLE viewer.foo(id int primary key); --Should be handled by separate set_config CREATE VIEW viewer.vw_foo AS SELECT * FROM viewer.foo; SELECT c.create_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name = 'test1' ORDER BY e.id DESC LIMIT 4; create_tags | set_name | ddl_sql_raw | ddl_sql_sent --------------------------------------------------------------------------------------------------------------+----------+----------------------------------------------+---------------------------------------------- {"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"} | test1 | CREATE VIEW viewer.vw_foo AS +| CREATE VIEW viewer.vw_foo AS + | | SELECT * FROM viewer.foo; | SELECT * FROM viewer.foo; {"ALTER TABLE","CREATE SEQUENCE","ALTER SEQUENCE","CREATE SCHEMA","CREATE TABLE","CREATE TYPE","ALTER TYPE"} | test1 | CREATE TABLE viewer.foo(id int primary key); | CREATE TABLE viewer.foo(id int primary key); {"ALTER TABLE","CREATE SEQUENCE","ALTER SEQUENCE","CREATE SCHEMA","CREATE TABLE","CREATE TYPE","ALTER TYPE"} | test1 | CREATE SCHEMA viewer; | CREATE SCHEMA viewer; {"ALTER TABLE","CREATE SEQUENCE","ALTER SEQUENCE","CREATE SCHEMA","CREATE TABLE","CREATE TYPE","ALTER TYPE"} | test1 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (4 rows) DROP VIEW viewer.vw_foo; DROP TABLE viewer.foo CASCADE; DROP SCHEMA viewer; SELECT c.drop_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name = 'test1' ORDER BY e.id DESC LIMIT 4; drop_tags | set_name | ddl_sql_raw | ddl_sql_sent ----------------------------------------------------------+----------+--------------------------------+-------------------------------- {"DROP SCHEMA","DROP TABLE","DROP TYPE","DROP SEQUENCE"} | test1 | DROP SCHEMA viewer; | DROP SCHEMA viewer; {"DROP SCHEMA","DROP TABLE","DROP TYPE","DROP SEQUENCE"} | test1 | DROP TABLE viewer.foo CASCADE; | DROP TABLE viewer.foo CASCADE; {"DROP VIEW","DROP FUNCTION"} | test1 | DROP VIEW viewer.vw_foo; | DROP VIEW viewer.vw_foo; {"DROP VIEW","DROP FUNCTION"} | test1 | CREATE VIEW viewer.vw_foo AS +| CREATE VIEW viewer.vw_foo AS + | | SELECT * FROM viewer.foo; | SELECT * FROM viewer.foo; (4 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/17_include_only_repset_tables_1.out000066400000000000000000000006051453171545300251670ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; --These kinds of repsets will not replicate CREATE events, only ALTER TABLE, so deploy after CREATE --We assume schema will be copied to subscriber separately CREATE SCHEMA special; CREATE TABLE special.foo (id serial primary key, foo text, bar text); CREATE TABLE special.bar (id serial primary key, super text, man text); pgl_ddl_deploy-2.2.1/expected/18_include_only_repset_tables_2.out000066400000000000000000000026151453171545300251740ustar00rootroot00000000000000SET client_min_messages = warning; SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.foo'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_1'; add_table_to_replication -------------------------- t (1 row) SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.bar'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_2'; add_table_to_replication -------------------------- t (1 row) --Deploy by set_name SELECT pgl_ddl_deploy.deploy('my_special_tables_1'); deploy -------- t (1 row) SELECT pgl_ddl_deploy.deploy('my_special_tables_2'); deploy -------- t (1 row) --Ensure these kinds of configs only have 'create' event triggers SELECT COUNT(1) FROM pg_event_trigger evt INNER JOIN pgl_ddl_deploy.event_trigger_schema ets ON evt.evtname IN(auto_replication_unsupported_trigger_name, ets.auto_replication_drop_trigger_name, ets.auto_replication_create_trigger_name) WHERE include_only_repset_tables; count ------- 2 (1 row) --Deploy by id SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'my_special_tables_1'; deploy -------- t (1 row) SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'my_special_tables_2'; deploy -------- t (1 row) pgl_ddl_deploy-2.2.1/expected/19_include_only_repset_tables_3.out000066400000000000000000000060621453171545300251760ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; ALTER TABLE special.foo ADD COLUMN happy TEXT; ALTER TABLE special.bar ADD COLUMN happier TEXT; SELECT c.create_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; create_tags | set_name | ddl_sql_raw | ddl_sql_sent -----------------+---------------------+--------------------------------------------------+-------------------------------------------------- {"ALTER TABLE"} | my_special_tables_2 | ALTER TABLE special.bar ADD COLUMN happier TEXT; | ALTER TABLE special.bar ADD COLUMN happier TEXT; {"ALTER TABLE"} | my_special_tables_1 | ALTER TABLE special.foo ADD COLUMN happy TEXT; | ALTER TABLE special.foo ADD COLUMN happy TEXT; (2 rows) --Test renaming which was missing in 1.2 ALTER TABLE special.foo RENAME COLUMN happy to happyz; ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz; ALTER TABLE special.foo RENAME COLUMN id TO id_2; ALTER TABLE special.bar RENAME COLUMN happier TO happierz; ALTER TABLE special.bar RENAME COLUMN id TO id_3; ALTER TABLE special.foo RENAME TO fooz; ALTER TABLE special.bar RENAME TO barz; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 20; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+------------------------------------------------------------+------------------------------------------------------------ my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; my_special_tables_2 | ALTER TABLE special.bar ADD COLUMN happier TEXT; | ALTER TABLE special.bar ADD COLUMN happier TEXT; my_special_tables_1 | ALTER TABLE special.foo ADD COLUMN happy TEXT; | ALTER TABLE special.foo ADD COLUMN happy TEXT; (9 rows) pgl_ddl_deploy-2.2.1/expected/20_include_only_repset_tables_4.out000066400000000000000000000131451453171545300251670ustar00rootroot00000000000000SET client_min_messages = warning; CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$ BEGIN RETURN NULL; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER noop BEFORE DELETE ON special.fooz FOR EACH ROW EXECUTE PROCEDURE noop(); ALTER TABLE special.fooz DISABLE TRIGGER noop; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+------------------------------------------------------------+------------------------------------------------------------ my_special_tables_1 | ALTER TABLE special.fooz DISABLE TRIGGER noop; | ALTER TABLE special.fooz DISABLE TRIGGER noop; my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; my_special_tables_2 | ALTER TABLE special.bar ADD COLUMN happier TEXT; | ALTER TABLE special.bar ADD COLUMN happier TEXT; my_special_tables_1 | ALTER TABLE special.foo ADD COLUMN happy TEXT; | ALTER TABLE special.foo ADD COLUMN happy TEXT; (10 rows) -- Test new subcommand functionality UPDATE pgl_ddl_deploy.set_configs SET exclude_alter_table_subcommands = pgl_ddl_deploy.common_exclude_alter_table_subcommands() WHERE include_only_repset_tables; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE include_only_repset_tables; deploy -------- t t (2 rows) SET client_min_messages = log; -- This should be ignored ALTER TABLE special.fooz ENABLE TRIGGER noop; LOG: Not processing DDL due to excluded subcommand(s): ENABLE TRIGGER: ALTER TABLE special.fooz ENABLE TRIGGER noop; -- This contains a tag we want to ignore but we can't separate out the parts - see the warning message ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); WARNING: Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: ADD CONSTRAINT, SQL: ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); ALTER TABLE special.fooz ADD COLUMN bar_id INT; -- This one should be ignored as well ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); LOG: Not processing DDL due to excluded subcommand(s): ADD CONSTRAINT: ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+--------------------------------------------------------------------------------+-------------------------------------------------------------------------------- my_special_tables_1 | ALTER TABLE special.fooz ADD COLUMN bar_id INT; | ALTER TABLE special.fooz ADD COLUMN bar_id INT; my_special_tables_2 | ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); | ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); my_special_tables_1 | ALTER TABLE special.fooz DISABLE TRIGGER noop; | ALTER TABLE special.fooz DISABLE TRIGGER noop; my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; (10 rows) SET client_min_messages = warning; DROP TABLE special.fooz CASCADE; DROP TABLE special.barz CASCADE; DROP SCHEMA special; pgl_ddl_deploy-2.2.1/expected/20_include_only_repset_tables_4_1.out000066400000000000000000000132011453171545300254000ustar00rootroot00000000000000SET client_min_messages = warning; CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$ BEGIN RETURN NULL; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER noop BEFORE DELETE ON special.fooz FOR EACH ROW EXECUTE PROCEDURE noop(); ALTER TABLE special.fooz DISABLE TRIGGER noop; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+------------------------------------------------------------+------------------------------------------------------------ my_special_tables_1 | ALTER TABLE special.fooz DISABLE TRIGGER noop; | ALTER TABLE special.fooz DISABLE TRIGGER noop; my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; my_special_tables_2 | ALTER TABLE special.bar ADD COLUMN happier TEXT; | ALTER TABLE special.bar ADD COLUMN happier TEXT; my_special_tables_1 | ALTER TABLE special.foo ADD COLUMN happy TEXT; | ALTER TABLE special.foo ADD COLUMN happy TEXT; (10 rows) -- Test new subcommand functionality UPDATE pgl_ddl_deploy.set_configs SET exclude_alter_table_subcommands = pgl_ddl_deploy.common_exclude_alter_table_subcommands() WHERE include_only_repset_tables; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE include_only_repset_tables; deploy -------- t t (2 rows) SET client_min_messages = log; -- This should be ignored ALTER TABLE special.fooz ENABLE TRIGGER noop; LOG: Not processing DDL due to excluded subcommand(s): ENABLE TRIGGER: ALTER TABLE special.fooz ENABLE TRIGGER noop; -- This contains a tag we want to ignore but we can't separate out the parts - see the warning message ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); WARNING: Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: ADD CONSTRAINT (and recurse), SQL: ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); ALTER TABLE special.fooz ADD COLUMN bar_id INT; -- This one should be ignored as well ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); LOG: Not processing DDL due to excluded subcommand(s): ADD CONSTRAINT (and recurse): ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+--------------------------------------------------------------------------------+-------------------------------------------------------------------------------- my_special_tables_1 | ALTER TABLE special.fooz ADD COLUMN bar_id INT; | ALTER TABLE special.fooz ADD COLUMN bar_id INT; my_special_tables_2 | ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); | ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); my_special_tables_1 | ALTER TABLE special.fooz DISABLE TRIGGER noop; | ALTER TABLE special.fooz DISABLE TRIGGER noop; my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; (10 rows) SET client_min_messages = warning; DROP TABLE special.fooz CASCADE; DROP TABLE special.barz CASCADE; DROP SCHEMA special; pgl_ddl_deploy-2.2.1/expected/21_unprivileged_users.out000066400000000000000000000001411453171545300232530ustar00rootroot00000000000000CREATE ROLE unpriv; SET ROLE unpriv; CREATE TEMP TABLE foo(); ALTER TABLE foo ADD COLUMN id INT; pgl_ddl_deploy-2.2.1/expected/22_is_deployed.out000066400000000000000000000100661453171545300216450ustar00rootroot00000000000000SET client_min_messages TO warning; --Test what is_deployed shows (introduced in 1.3) SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; set_name | is_deployed ---------------------+------------- test1 | t test2 | t test3 | t test4 | t test5 | t test6 | t test7 | t test8 | t test1 | t my_special_tables_1 | t my_special_tables_2 | t (11 rows) SELECT pgl_ddl_deploy.undeploy(id) FROM pgl_ddl_deploy.set_configs; undeploy ---------- t t t t t t t t t t t (11 rows) SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; set_name | is_deployed ---------------------+------------- test1 | f test2 | f test3 | f test4 | f test5 | f test6 | f test7 | f test8 | f test1 | f my_special_tables_1 | f my_special_tables_2 | f (11 rows) --Nothing should replicate this CREATE TABLE foobar (id serial primary key); DROP TABLE foobar; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------+---------------------------------- test4 | DROP SCHEMA special; | DROP SCHEMA special; test3 | DROP SCHEMA special; | DROP SCHEMA special; test2 | DROP SCHEMA special; | DROP SCHEMA special; test1 | DROP SCHEMA special; | DROP SCHEMA special; test4 | DROP TABLE special.barz CASCADE; | DROP TABLE special.barz CASCADE; test3 | DROP TABLE special.barz CASCADE; | DROP TABLE special.barz CASCADE; test2 | DROP TABLE special.barz CASCADE; | DROP TABLE special.barz CASCADE; test1 | DROP TABLE special.barz CASCADE; | DROP TABLE special.barz CASCADE; test4 | DROP TABLE special.fooz CASCADE; | DROP TABLE special.fooz CASCADE; test3 | DROP TABLE special.fooz CASCADE; | DROP TABLE special.fooz CASCADE; (10 rows) --Re-deploy and check again what shows as deployed SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs; deploy -------- t t t t t t t t t t t (11 rows) SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; set_name | is_deployed ---------------------+------------- test1 | t test2 | t test3 | t test4 | t test5 | t test6 | t test7 | t test8 | t test1 | t my_special_tables_1 | t my_special_tables_2 | t (11 rows) CREATE TABLE foobar (id serial primary key); DROP TABLE foobar CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test4 | DROP TABLE foobar CASCADE; | DROP TABLE foobar CASCADE; test3 | DROP TABLE foobar CASCADE; | DROP TABLE foobar CASCADE; test2 | DROP TABLE foobar CASCADE; | DROP TABLE foobar CASCADE; test1 | DROP TABLE foobar CASCADE; | DROP TABLE foobar CASCADE; test4 | CREATE TABLE foobar (id serial primary key); | CREATE TABLE foobar (id serial primary key); test3 | CREATE TABLE foobar (id serial primary key); | CREATE TABLE foobar (id serial primary key); test2 | CREATE TABLE foobar (id serial primary key); | CREATE TABLE foobar (id serial primary key); test1 | CREATE TABLE foobar (id serial primary key); | CREATE TABLE foobar (id serial primary key); test4 | DROP SCHEMA special; | DROP SCHEMA special; test3 | DROP SCHEMA special; | DROP SCHEMA special; (10 rows) pgl_ddl_deploy-2.2.1/expected/23_1_4_features.out000066400000000000000000000064541453171545300216350ustar00rootroot00000000000000SET client_min_messages = warning; -- We need to eventually test this on a real subscriber SET search_path TO ''; CREATE SCHEMA bla; -- We test the subcommand feature with the other repset_table tests SELECT pgl_ddl_deploy.undeploy(id) FROM pgl_ddl_deploy.set_configs; undeploy ---------- t t t t t t t t t t t (11 rows) CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('test_ddl_only'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, ddl_only_replication) VALUES ('test_ddl_only','^super.*',false); -- It is now permitted to have multiple set_configs for same set_name if using ddl_only_replication INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, ddl_only_replication) VALUES ('test_ddl_only','^duper.*',true); SET ROLE postgres; SELECT pgl_ddl_deploy.deploy('test_ddl_only'); deploy -------- t (1 row) -- The difference here is that the latter table is under ddl_only_replication SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA super; CREATE TABLE super.man(id serial primary key); CREATE SCHEMA duper; CREATE TABLE duper.man(id serial primary key); -- Now assume we just want to replicate structure going forward ONLY ALTER TABLE super.man ADD COLUMN foo text; ALTER TABLE duper.man ADD COLUMN foo text; -- No cascade required for second drop because it was not added to replication DROP TABLE super.man CASCADE; DROP TABLE duper.man; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.ddl_only_replication FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | ddl_only_replication ---------------+------------------------------------------------+------------------------------------------------+---------------------- test_ddl_only | DROP TABLE duper.man; | DROP TABLE duper.man; | t test_ddl_only | DROP TABLE super.man CASCADE; | DROP TABLE super.man CASCADE; | f test_ddl_only | ALTER TABLE duper.man ADD COLUMN foo text; | ALTER TABLE duper.man ADD COLUMN foo text; | t test_ddl_only | ALTER TABLE super.man ADD COLUMN foo text; | ALTER TABLE super.man ADD COLUMN foo text; | f test_ddl_only | CREATE TABLE duper.man(id serial primary key); | CREATE TABLE duper.man(id serial primary key); | t test_ddl_only | CREATE SCHEMA duper; | CREATE SCHEMA duper; | t test_ddl_only | CREATE TABLE super.man(id serial primary key); | CREATE TABLE super.man(id serial primary key); | f test_ddl_only | CREATE SCHEMA super; | CREATE SCHEMA super; | f test4 | CREATE SCHEMA bla; | CREATE SCHEMA bla; | f test3 | CREATE SCHEMA bla; | CREATE SCHEMA bla; | f (10 rows) pgl_ddl_deploy-2.2.1/expected/24_sub_retries.out000066400000000000000000014710671453171545300217120ustar00rootroot00000000000000--****NOTE*** this file drops the whole extension and all previous test setup. --If adding new tests, it is best to keep this file as the last test before cleanup. SET client_min_messages = warning; SELECT pubnames, message_type, regexp_replace(regexp_replace(regexp_replace(message::text, 'p_pid := (\d+)', 'p_pid := ?'), 'p_provider_name := (NULL|''\w+'')', 'p_provider_name := ?'), 'p_driver := (''\w+'')', 'p_driver := ?') as message FROM all_queues() WHERE NOT message::text LIKE '%notify_subscription_refresh%' ORDER BY queued_at; pubnames | message_type | message -----------------------+--------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n /***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n /***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foobar.foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foobar.foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foobar.foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foobar.foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foobar.foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foobar.foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foobar.foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foobar.foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foobar.foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foobar.foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foobar.foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foobar.foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foobar.foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foobar.foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foobar.foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foobar.foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW foobar.fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW foobar.fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW foobar.fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW foobar.fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW foobar.fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW foobar.fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW foobar.fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW foobar.fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW foobar.barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW foobar.barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW foobar.barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW foobar.barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW foobar.barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW foobar.barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW foobar.barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW foobar.barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foobar.foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foobar.foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foobar.foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foobar.foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foobar.foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foobar.foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foobar.foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foobar.foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ CREATE TABLE foo(id int primary key); $PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo(id int primary key); \n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foobar.foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ CREATE TABLE foobar.foo(id int primary key); $PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foobar.foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key); \n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foobar.foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ CREATE TABLE foobar.foo(id int primary key); $PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foobar.foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key); \n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo, foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo, foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo, foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo, foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo, foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo, foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo, foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo, foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n /***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n /***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA viewer;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA viewer;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA viewer;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA viewer;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'viewer',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE viewer.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE viewer.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'viewer',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE viewer.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE viewer.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'viewer',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE viewer.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE viewer.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'viewer',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE viewer.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE viewer.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'viewer',\n p_relname := 'vw_foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 11,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'viewer',\n p_relname := 'vw_foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'viewer',\n p_relname := 'vw_foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'viewer',\n p_relname := 'vw_foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW viewer.vw_foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW viewer.vw_foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 11,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW viewer.vw_foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW viewer.vw_foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW viewer.vw_foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW viewer.vw_foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW viewer.vw_foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW viewer.vw_foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE viewer.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE viewer.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE viewer.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE viewer.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE viewer.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE viewer.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE viewer.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE viewer.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA viewer;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA viewer;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA viewer;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA viewer;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA special;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA special;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA special;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA special;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE special.foo (id serial primary key, foo text, bar text);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE special.foo (id serial primary key, foo text, bar text);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'bar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.bar (id serial primary key, super text, man text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE special.bar (id serial primary key, super text, man text);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'bar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.bar (id serial primary key, super text, man text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE special.bar (id serial primary key, super text, man text);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'bar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.bar (id serial primary key, super text, man text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE special.bar (id serial primary key, super text, man text);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'bar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.bar (id serial primary key, super text, man text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE special.bar (id serial primary key, super text, man text);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD COLUMN happy TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD COLUMN happy TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD COLUMN happy TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo ADD COLUMN happy TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo ADD COLUMN happy TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar ADD COLUMN happier TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar ADD COLUMN happier TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar ADD COLUMN happier TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar ADD COLUMN happier TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar ADD COLUMN happier TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME COLUMN happy to happyz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME COLUMN happy to happyz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME COLUMN id TO id_2;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME COLUMN id TO id_2;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME COLUMN happier TO happierz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME COLUMN happier TO happierz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME COLUMN id TO id_3;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME COLUMN id TO id_3;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME TO fooz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME TO fooz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME TO fooz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME TO fooz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME TO fooz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME TO barz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME TO barz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME TO barz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME TO barz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME TO barz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 11,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz DISABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz DISABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz DISABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz DISABLE TRIGGER noop;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz DISABLE TRIGGER noop;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ENABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ENABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ENABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ENABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ENABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ENABLE TRIGGER noop;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ENABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ENABLE TRIGGER noop;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ADD COLUMN bar_id INT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ADD COLUMN bar_id INT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.fooz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE special.fooz CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.fooz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE special.fooz CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.fooz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE special.fooz CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.fooz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE special.fooz CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.barz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE special.barz CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.barz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE special.barz CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.barz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE special.barz CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.barz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE special.barz CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA special;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA special;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA special;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA special;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foobar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar (id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar (id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foobar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar (id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar (id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foobar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar (id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar (id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foobar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar (id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar (id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA bla;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA bla;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA bla;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA bla;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA bla;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE SCHEMA bla;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA bla;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE SCHEMA bla;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA super;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE SCHEMA super;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 19,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := 'super',\n p_relname := 'man_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE super.man(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE TABLE super.man(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 19,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA duper;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE SCHEMA duper;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 20,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := 'duper',\n p_relname := 'man_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE duper.man(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE TABLE duper.man(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 20,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := 'super',\n p_relname := 'man',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE super.man ADD COLUMN foo text;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n ALTER TABLE super.man ADD COLUMN foo text;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 19,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := 'duper',\n p_relname := 'man',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE duper.man ADD COLUMN foo text;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n ALTER TABLE duper.man ADD COLUMN foo text;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 20,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE super.man CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n DROP TABLE super.man CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 19,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE duper.man;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n DROP TABLE duper.man;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 20,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " (432 rows) DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 AND NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN SELECT COUNT(1) INTO v_ct FROM all_queues() WHERE message::text LIKE '%notify_subscription_refresh%'; IF v_ct != 79 THEN RAISE EXCEPTION '%', v_ct; END IF; END IF; END$$; --Some day, we should regress with multiple databases. There are examples of this in pglogical code base --For now, we will mock the subscriber behavior, which is less than ideal, because it misses testing execution --on subscriber DROP OWNED BY test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy_nopriv; DROP EXTENSION pgl_ddl_deploy CASCADE; CREATE EXTENSION pgl_ddl_deploy; SET SESSION_REPLICATION_ROLE TO REPLICA; --To ensure testing subscriber behavior CREATE ROLE test_pgl_ddl_deploy; GRANT CREATE ON DATABASE contrib_regression TO test_pgl_ddl_deploy; SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname = 'test_pgl_ddl_deploy'; add_role ---------- t (1 row) SET ROLE test_pgl_ddl_deploy; --Mock subscriber_log insert which should take place on subscriber error when option enabled INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 100, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW joy AS SELECT * FROM joyous', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous;', FALSE, 'relation "joyous" does not exist'); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {f} (1 row) SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; retry_subscriber_log ---------------------- f (1 row) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+-----------------------------------------+------------------------------------------------------------------------+-----------+---------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist (3 rows) CREATE TABLE joyous (id int); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {t} (1 row) SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- (1 row) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+-----------------------------------------+------------------------------------------------------------------------+-----------+---------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 4 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 4 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | t | (4 rows) --Now let's do 2 INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 101, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW happy AS SELECT * FROM happier;', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier;', FALSE, 'relation "happier" does not exist'); INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 102, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW glee AS SELECT * FROM gleeful;', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful;', FALSE, 'relation "gleeful" does not exist'); --The first fails and the second therefore is not attempted SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {f} (1 row) --Both fail if we try each separately SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; retry_subscriber_log ---------------------- f f (2 rows) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+---------------------------------------------+---------------------------------------------------------------------------+-----------+----------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 4 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 4 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | t | 5 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 7 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 6 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 9 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 7 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 8 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 8 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 9 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist (9 rows) --One succeeds, one fails CREATE TABLE happier (id int); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {t,f} (1 row) --One fails SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {f} (1 row) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+---------------------------------------------+---------------------------------------------------------------------------+-----------+----------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 4 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 4 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | t | 5 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 7 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 6 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 9 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 7 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 8 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 8 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 10 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 9 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 11 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 10 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | t | 11 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 12 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 12 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist (12 rows) --Succeed with new id CREATE TABLE gleeful (id int); SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; retry_subscriber_log ---------------------- t (1 row) --Nothing SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; retry_subscriber_log ---------------------- (0 rows) SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- (1 row) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+---------------------------------------------+---------------------------------------------------------------------------+-----------+----------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 4 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 4 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | t | 5 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 7 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 6 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 9 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 7 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 8 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 8 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 10 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 9 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 11 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 10 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | t | 11 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 12 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 12 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 13 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 13 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | t | (13 rows) DROP TABLE joyous CASCADE; DROP TABLE happier CASCADE; DROP TABLE gleeful CASCADE; pgl_ddl_deploy-2.2.1/expected/25_1_5_features.out000066400000000000000000000413431453171545300216340ustar00rootroot00000000000000-- Suppress pid-specific warning messages SET client_min_messages TO error; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test1','.*',true, true); -- It's generally good to use queue_subscriber_failures with include_everything, so a bogus grant won't break replication on subscriber INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_everything, queue_subscriber_failures, create_tags) VALUES ('test1',true, true, '{GRANT,REVOKE}'); SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; deploy -------- t t (2 rows) DISCARD TEMP; SET search_path TO public; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key, bla int); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------------+--------------------------------------------------- test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); (1 row) GRANT SELECT ON foo TO PUBLIC; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | include_everything ----------+---------------------------------------------------+---------------------------------------------------+-------------------- test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f (2 rows) INSERT INTO foo (bla) VALUES (1),(2),(3); REVOKE INSERT ON foo FROM PUBLIC; DROP TABLE foo CASCADE; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | include_everything ----------+---------------------------------------------------+---------------------------------------------------+-------------------- test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; | f test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; | t test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f (4 rows) SELECT * FROM pgl_ddl_deploy.unhandled; id | set_name | pid | executed_at | ddl_sql_raw | command_tag | reason | txid | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+-------------+-------------+--------+------+---------------+----------+---------------- (0 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) /***** Test cancel and terminate blocker functionality *****/ SET ROLE postgres; UPDATE pgl_ddl_deploy.set_configs SET lock_safe_deployment = FALSE, signal_blocking_subscriber_sessions = 'cancel'; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; deploy -------- t t (2 rows) SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key, bla int); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------------+--------------------------------------------------- test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); (5 rows) GRANT SELECT ON foo TO PUBLIC; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | include_everything ----------+---------------------------------------------------+---------------------------------------------------+-------------------- test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; | f test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; | t test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f (6 rows) INSERT INTO foo (bla) VALUES (1),(2),(3); REVOKE INSERT ON foo FROM PUBLIC; DROP TABLE foo CASCADE; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | include_everything ----------+---------------------------------------------------+---------------------------------------------------+-------------------- test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; | f test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; | t test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; | f test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; | t test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f (8 rows) SELECT * FROM pgl_ddl_deploy.unhandled; id | set_name | pid | executed_at | ddl_sql_raw | command_tag | reason | txid | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+-------------+-------------+--------+------+---------------+----------+---------------- (0 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) CREATE TABLE public.foo(id serial primary key, bla int); CREATE TABLE public.foo2 () INHERITS (public.foo); CREATE TABLE public.bar(id serial primary key, bla int); \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('cancel','public','foo'); signal | successful | state | query | reported | pg_sleep --------+------------+--------+-------------------------------------------------------+----------+---------- cancel | t | active | BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30); | f | (1 row) \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('terminate','public','foo'); signal | successful | state | query | reported | pg_sleep -----------+------------+--------+-------------------------------------------------------+----------+---------- terminate | t | active | BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30); | f | (1 row) \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & -- This process should not be killed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; INSERT INTO public.bar (bla) VALUES (1); SELECT pg_sleep(2); COMMIT;" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ADD COLUMN bar text;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ADD COLUMN bar text; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'cancel', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); subscriber_command -------------------- t (1 row) TABLE public.foo; id | bla | bar ----+-----+----- (0 rows) -- Now two processes to be killed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) -- This process will wait for the one above - but we want it to fail regardless of which gets killed first -- Avoid it firing our event triggers by using session_replication_role = replica \! PGOPTIONS='--client-min-messages=warning --session-replication-role=replica' psql -d contrib_regression -c "BEGIN; ALTER TABLE public.foo DROP COLUMN bar; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(2); pg_sleep ---------- (1 row) SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ADD COLUMN super text;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ADD COLUMN super text; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'terminate', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); subscriber_command -------------------- t (1 row) TABLE public.foo; id | bla | bar | super ----+-----+-----+------- (0 rows) /**** Try cancel_then_terminate, which should first try to cancel ****/ -- This process should be killed \! echo "BEGIN; SELECT * FROM public.foo;\n\! sleep 15" | psql contrib_regression > /dev/null 2>&1 & -- This process should not be killed \! psql contrib_regression -c "BEGIN; INSERT INTO public.bar (bla) VALUES (1); SELECT pg_sleep(5); COMMIT;" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ALTER COLUMN bar SET NOT NULL;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ALTER COLUMN bar SET NOT NULL; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'cancel_then_terminate', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); subscriber_command -------------------- t (1 row) TABLE public.foo; id | bla | bar | super ----+-----+-----+------- (0 rows) /*** TEST INHERITANCE AND PARTITIONING ***/ -- Same workflow as above, but instead select from child, alter parent \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo2; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('terminate','public','foo'); signal | successful | state | query | reported | pg_sleep -----------+------------+--------+--------------------------------------------------------+----------+---------- terminate | t | active | BEGIN; SELECT * FROM public.foo2; SELECT pg_sleep(30); | f | (1 row) /*** With <=1.5, it showed this. But it should kill the process. signal | successful | state | query | reported | pg_sleep --------+------------+-------+-------+----------+---------- (0 rows) ***/ DROP TABLE public.foo CASCADE; TABLE bar; id | bla ----+----- 1 | 1 2 | 1 (2 rows) DROP TABLE public.bar CASCADE; SELECT signal, successful, state, query, reported FROM pgl_ddl_deploy.killed_blockers ORDER BY signal, query; signal | successful | state | query | reported -----------+------------+---------------------+---------------------------------------------------------------------+---------- cancel | t | active | BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30); | f cancel | t | idle in transaction | SELECT * FROM public.foo; | f terminate | t | active | BEGIN; ALTER TABLE public.foo DROP COLUMN bar; SELECT pg_sleep(30); | f terminate | t | active | BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30); | f terminate | t | idle in transaction | SELECT * FROM public.foo; | f (5 rows) SELECT pg_sleep(1); pg_sleep ---------- (1 row) -- Should be zero - everything was killed SELECT COUNT(1) FROM pg_stat_activity WHERE usename = session_user AND NOT pid = pg_backend_pid() AND query LIKE '%public.foo%'; count ------- 0 (1 row) pgl_ddl_deploy-2.2.1/expected/26_new_setup.out000066400000000000000000000055171453171545300213670ustar00rootroot00000000000000--****NOTE*** this file drops the whole extension and all previous test setup. --If adding new tests, it is best to keep this file as the last test before cleanup. SET client_min_messages = warning; --Some day, we should regress with multiple databases. There are examples of this in pglogical code base --For now, we will mock the subscriber behavior, which is less than ideal, because it misses testing execution --on subscriber DROP EXTENSION pgl_ddl_deploy CASCADE; -- This test has been rewritten and presently exists for historical reasons and to maintain configuration CREATE EXTENSION pgl_ddl_deploy; --These are the same sets as in the new_set_behavior.sql INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_2', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; --One include_schema_regex one that should be unchanged CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('testspecial'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('testspecial','^special$',true, true); SELECT pgl_ddl_deploy.deploy('testspecial'); deploy -------- t (1 row) --These kinds of repsets will not replicate CREATE events, only ALTER TABLE, so deploy after CREATE --We assume schema will be copied to subscriber separately CREATE SCHEMA special; CREATE TABLE special.foo (id serial primary key, foo text, bar text); CREATE TABLE special.bar (id serial primary key, super text, man text); SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.foo'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_1'; add_table_to_replication -------------------------- t (1 row) SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.bar'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_2'; add_table_to_replication -------------------------- t (1 row) --Deploy by set_name SELECT pgl_ddl_deploy.deploy('my_special_tables_1'); deploy -------- t (1 row) SELECT pgl_ddl_deploy.deploy('my_special_tables_2'); deploy -------- t (1 row) pgl_ddl_deploy-2.2.1/expected/27_raise_message.out000066400000000000000000000020771453171545300221640ustar00rootroot00000000000000SET client_min_messages TO WARNING; ALTER EXTENSION pgl_ddl_deploy UPDATE; -- Simple example SELECT pgl_ddl_deploy.raise_message('WARNING', 'foo'); WARNING: foo raise_message --------------- t (1 row) -- Test case that needs % escapes SELECT pgl_ddl_deploy.raise_message('WARNING', $$SELECT foo FROM bar WHERE baz LIKE 'foo%'$$); WARNING: SELECT foo FROM bar WHERE baz LIKE 'foo%' raise_message --------------- t (1 row) /*** Failing message on 1.5 read: ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 3 SQL statement " DO $block$ BEGIN RAISE WARNING $pgl_ddl_deploy_msg$SELECT foo FROM bar WHERE baz LIKE 'foo%'$pgl_ddl_deploy_msg$; END$block$; " PL/pgSQL function pgl_ddl_deploy.raise_message(text,text) line 4 at EXECUTE ***/ SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/28_1_6_features.out000066400000000000000000000040671453171545300216420ustar00rootroot00000000000000-- Configure this to only replicate functions or views -- This test is to ensure the config does NOT auto-add tables to replication (bug with <=1.5) UPDATE pgl_ddl_deploy.set_configs SET create_tags = '{"CREATE FUNCTION","ALTER FUNCTION","CREATE VIEW","ALTER VIEW"}' , drop_tags = '{"DROP FUNCTION","DROP VIEW"}' WHERE set_name = 'testspecial'; SELECT pgl_ddl_deploy.deploy('testspecial'); NOTICE: table "tmp_objs" does not exist, skipping deploy -------- t (1 row) CREATE TEMP VIEW tables_in_replication AS SELECT COUNT(1) FROM pgl_ddl_deploy.rep_set_table_wrapper() t WHERE t.name = 'testspecial' AND NOT relid::REGCLASS::TEXT = 'pgl_ddl_deploy.queue'; TABLE tables_in_replication; count ------- 2 (1 row) CREATE TABLE special.do_not_replicate_me(id int primary key); TABLE tables_in_replication; count ------- 2 (1 row) -- In <=1.5, this would have hit the code path to add new tables to replication, even though -- the set is configured not to replicate CREATE TABLE events CREATE FUNCTION special.do_replicate_me() RETURNS INT AS 'SELECT 1' LANGUAGE SQL; -- This SHOULD show the same as above, but showed 1 more table in <=1.5 TABLE tables_in_replication; count ------- 2 (1 row) -- Test to ensure we are only setting these defaults (trigger set_tag_defaults) on INSERT UPDATE pgl_ddl_deploy.set_configs SET drop_tags = NULL WHERE set_name = 'testspecial' RETURNING drop_tags; drop_tags ----------- (1 row) /* In <= 1.5, returned this: drop_tags -------------------------------------------------------------------------------------- {"DROP SCHEMA","DROP TABLE","DROP FUNCTION","DROP TYPE","DROP VIEW","DROP SEQUENCE"} (1 row) */ SET client_min_messages TO warning; DROP OWNED BY test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy; DROP ROLE unpriv; DROP EXTENSION pgl_ddl_deploy CASCADE; DROP EXTENSION IF EXISTS pglogical CASCADE; DROP SCHEMA IF EXISTS pglogical CASCADE; DROP TABLE IF EXISTS tmp_objs; DROP SCHEMA IF EXISTS special CASCADE; DROP SCHEMA IF EXISTS bla CASCADE; DROP SCHEMA IF EXISTS pgl_ddl_deploy CASCADE; pgl_ddl_deploy-2.2.1/expected/29_create_ext.out000066400000000000000000000017021453171545300214740ustar00rootroot00000000000000-- Allow running regression suite with upgrade paths \set v `echo ${FROMVERSION:-2.2}` SET client_min_messages = warning; CREATE TEMP TABLE v AS SELECT :'v'::TEXT AS num; DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 AND (SELECT num FROM v) != ALL('{1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7}'::text[]) THEN RAISE LOG '%', 'USING NATIVE'; ELSE CREATE EXTENSION pglogical; END IF; END$$; DROP TABLE v; CREATE EXTENSION pgl_ddl_deploy VERSION :'v'; CREATE FUNCTION set_driver() RETURNS VOID AS $BODY$ BEGIN IF current_setting('server_version_num')::INT >= 100000 AND (SELECT extversion::numeric FROM pg_extension WHERE extname = 'pgl_ddl_deploy') >= 2.0 THEN ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN driver SET DEFAULT 'native'::pgl_ddl_deploy.driver; UPDATE pgl_ddl_deploy.set_configs SET driver = 'native'::pgl_ddl_deploy.driver; END IF; END; $BODY$ LANGUAGE plpgsql; SELECT set_driver(); set_driver ------------ (1 row) pgl_ddl_deploy-2.2.1/expected/30_setup.out000066400000000000000000000055261453171545300205110ustar00rootroot00000000000000DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION test1; CREATE PUBLICATION test2; CREATE PUBLICATION test3; CREATE PUBLICATION test4; CREATE PUBLICATION test5; CREATE PUBLICATION test6; CREATE PUBLICATION test7; CREATE PUBLICATION test8;$sql$; ELSE CREATE TEMP TABLE foonode AS SELECT pglogical.create_node('test','host=localhost'); DROP TABLE foonode; CREATE TEMP TABLE repsets AS 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 FROM sets s; DROP TABLE repsets; END IF; END$$; CREATE ROLE test_pgl_ddl_deploy LOGIN; GRANT CREATE ON DATABASE contrib_regression TO test_pgl_ddl_deploy; SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname = 'test_pgl_ddl_deploy'; add_role ---------- t (1 row) SET ROLE test_pgl_ddl_deploy; CREATE FUNCTION check_rep_tables() RETURNS TABLE (set_name TEXT, table_name TEXT) AS $BODY$ BEGIN -- Handle change from view to function rep_set_table_wrapper IF (SELECT extversion FROM pg_extension WHERE extname = 'pgl_ddl_deploy') = ANY('{1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7}'::text[]) THEN RETURN QUERY EXECUTE $$ SELECT set_name::TEXT, set_reloid::TEXT AS table_name FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) ORDER BY set_name::TEXT, set_reloid::TEXT;$$; ELSE RETURN QUERY EXECUTE $$ SELECT name::TEXT AS set_name, relid::regclass::TEXT AS table_name FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE relid::regclass::TEXT <> 'pgl_ddl_deploy.queue' ORDER BY name::TEXT, relid::TEXT;$$; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION all_queues() RETURNS TABLE (queued_at timestamp with time zone, role name, pubnames text[], message_type "char", -- we use json here to provide test output consistency whether native or pglogical message json) AS $BODY$ BEGIN IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY EXECUTE $$ SELECT queued_at, role, replication_sets AS pubnames, message_type, message FROM pglogical.queue UNION ALL SELECT queued_at, role, pubnames, message_type, to_json(message) FROM pgl_ddl_deploy.queue;$$; ELSE RETURN QUERY EXECUTE $$ SELECT queued_at, role, pubnames, message_type, to_json(message) AS message FROM pgl_ddl_deploy.queue; $$; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION verify_count(ct int, expected int) RETURNS BOOLEAN AS $BODY$ BEGIN RAISE LOG 'ct: %', ct; IF ct != expected THEN RAISE EXCEPTION 'Count % does not match expected count of %', ct, expected; END IF; RETURN TRUE; END$BODY$ LANGUAGE plpgsql; pgl_ddl_deploy-2.2.1/expected/31_add_configs.out000066400000000000000000000027251453171545300216100ustar00rootroot00000000000000INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test1','.*',true, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test2','.*',true, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test3','.*',false, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test4','.*',false, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test5','^foo.*',true, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test6','^foo.*',true, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test7','^foo.*',false, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test8','^foo.*',false, false); --Ensure regex must be valid INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test9','^foo.*((',false, false); ERROR: invalid regular expression: parentheses () not balanced pgl_ddl_deploy-2.2.1/expected/32_deploy_update.out000066400000000000000000000043461453171545300222100ustar00rootroot00000000000000--This will show different warnings depending on if we are actually updating to new version or not SET client_min_messages = error; ALTER EXTENSION pgl_ddl_deploy UPDATE; SELECT set_driver(); set_driver ------------ (1 row) SELECT pgl_ddl_deploy.deploy('test1'); deploy -------- t (1 row) DO $$ DECLARE v_rec RECORD; BEGIN FOR v_rec IN SELECT name FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name LIKE 'test%' AND name <> 'test1' ORDER BY name LOOP PERFORM pgl_ddl_deploy.deploy(v_rec.name); END LOOP; END$$; --Now that we are on highest version, ensure WARNING shows DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION testtemp;$sql$; ELSE CREATE TEMP TABLE repset AS SELECT pglogical.create_replication_set (set_name:='testtemp' ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE); DROP TABLE repset; END IF; END$$; SET client_min_messages = warning; BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (id, set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES (999, 'testtemp','.*',true, true); CREATE TABLE break(id serial primary key); SELECT pgl_ddl_deploy.deploy('testtemp'); WARNING: Deployment of auto-replication for id 999 set_name testtemp failed because 1 tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '.*' AND n.nspname !~* '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = 'testtemp' AND rsr.relid = c.oid AND rsr.driver = (SELECT driver FROM pgl_ddl_deploy.set_configs WHERE set_name = 'testtemp')); deploy -------- f (1 row) ROLLBACK; pgl_ddl_deploy-2.2.1/expected/33_allowed.out000066400000000000000000001253571453171545300210100ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; SET client_min_messages TO warning; /*** In default schema **/ CREATE TABLE foo(id serial primary key); SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ test1 | foo test2 | foo test3 | foo test4 | foo (4 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test2 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test1 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); (4 rows) ALTER TABLE foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test2 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test1 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); (8 rows) INSERT INTO foo (bla) VALUES (1),(2),(3); DROP TABLE foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); (10 rows) CREATE FUNCTION foo() RETURNS INT AS $BODY$ SELECT 1; $BODY$ LANGUAGE SQL STABLE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+--------------------------------------+-------------------------------------- test4 | CREATE FUNCTION foo() RETURNS INT AS+| CREATE FUNCTION foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test3 | CREATE FUNCTION foo() RETURNS INT AS+| CREATE FUNCTION foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test2 | CREATE FUNCTION foo() RETURNS INT AS+| CREATE FUNCTION foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test1 | CREATE FUNCTION foo() RETURNS INT AS+| CREATE FUNCTION foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; (10 rows) ALTER FUNCTION foo() OWNER TO current_user; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test4 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test3 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test2 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test1 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test4 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test3 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test2 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test1 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; (10 rows) DROP FUNCTION foo(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test4 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test3 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test2 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test1 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test4 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test3 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test2 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test1 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test4 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test3 | CREATE FUNCTION foo() RETURNS INT AS +| CREATE FUNCTION foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; (10 rows) CREATE VIEW fooview AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test4 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test3 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test2 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test1 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test4 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test3 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test2 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test1 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test4 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; test3 | ALTER FUNCTION foo() OWNER TO current_user; | ALTER FUNCTION foo() OWNER TO current_user; (10 rows) ALTER VIEW fooview RENAME TO barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------+--------------------------------------- test4 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test3 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test2 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test1 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test4 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test3 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test2 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test1 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test4 | DROP FUNCTION foo(); | DROP FUNCTION foo(); test3 | DROP FUNCTION foo(); | DROP FUNCTION foo(); (10 rows) DROP VIEW barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------+--------------------------------------- test4 | DROP VIEW barview; | DROP VIEW barview; test3 | DROP VIEW barview; | DROP VIEW barview; test2 | DROP VIEW barview; | DROP VIEW barview; test1 | DROP VIEW barview; | DROP VIEW barview; test4 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test3 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test2 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test1 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test4 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test3 | CREATE VIEW fooview AS +| CREATE VIEW fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; (10 rows) CREATE SEQUENCE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------+--------------------------------------- test4 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test3 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test2 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test1 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test4 | DROP VIEW barview; | DROP VIEW barview; test3 | DROP VIEW barview; | DROP VIEW barview; test2 | DROP VIEW barview; | DROP VIEW barview; test1 | DROP VIEW barview; | DROP VIEW barview; test4 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; test3 | ALTER VIEW fooview RENAME TO barview; | ALTER VIEW fooview RENAME TO barview; (10 rows) ALTER SEQUENCE foo RESTART; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test4 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test3 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test2 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test1 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test4 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test3 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test2 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test1 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test4 | DROP VIEW barview; | DROP VIEW barview; test3 | DROP VIEW barview; | DROP VIEW barview; (10 rows) DROP SEQUENCE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test4 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test3 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test2 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test1 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test4 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test3 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test2 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test1 | ALTER SEQUENCE foo RESTART; | ALTER SEQUENCE foo RESTART; test4 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; test3 | CREATE SEQUENCE foo; | CREATE SEQUENCE foo; (10 rows) CREATE SCHEMA foobar; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------+----------------------- test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test4 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test3 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test2 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test1 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test4 | DROP SEQUENCE foo; | DROP SEQUENCE foo; test3 | DROP SEQUENCE foo; | DROP SEQUENCE foo; (10 rows) CREATE TABLE foobar.foo(id serial primary key); SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ test1 | foobar.foo test2 | foobar.foo test3 | foobar.foo test4 | foobar.foo test5 | foobar.foo test6 | foobar.foo test7 | foobar.foo test8 | foobar.foo (8 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------+------------------------------------------------- test8 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test7 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test6 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test5 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test4 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test3 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test2 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test1 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; (10 rows) ALTER TABLE foobar.foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------+------------------------------------------------- test8 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test7 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test6 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test5 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test4 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test8 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test7 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); (10 rows) INSERT INTO foobar.foo (bla) VALUES (1),(2),(3); DROP TABLE foobar.foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test2 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test1 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test8 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test7 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; (10 rows) CREATE FUNCTION foobar.foo() RETURNS INT AS $BODY$ SELECT 1; $BODY$ LANGUAGE SQL STABLE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test8 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test7 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test6 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test5 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test4 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test3 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test2 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test1 | CREATE FUNCTION foobar.foo() RETURNS INT AS+| CREATE FUNCTION foobar.foo() RETURNS INT AS+ | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) ALTER FUNCTION foobar.foo() OWNER TO current_user; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------------+---------------------------------------------------- test8 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test7 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test6 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test5 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test4 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test3 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test2 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test1 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test8 | CREATE FUNCTION foobar.foo() RETURNS INT AS +| CREATE FUNCTION foobar.foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; test7 | CREATE FUNCTION foobar.foo() RETURNS INT AS +| CREATE FUNCTION foobar.foo() RETURNS INT AS + | $BODY$ +| $BODY$ + | SELECT 1; +| SELECT 1; + | $BODY$ +| $BODY$ + | LANGUAGE SQL STABLE; | LANGUAGE SQL STABLE; (10 rows) DROP FUNCTION foobar.foo(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------------+---------------------------------------------------- test8 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test7 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test6 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test5 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test4 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test3 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test2 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test1 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test8 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; test7 | ALTER FUNCTION foobar.foo() OWNER TO current_user; | ALTER FUNCTION foobar.foo() OWNER TO current_user; (10 rows) CREATE VIEW foobar.fooview AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------+------------------------------- test8 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test7 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test6 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test5 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test4 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test3 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test2 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test1 | CREATE VIEW foobar.fooview AS+| CREATE VIEW foobar.fooview AS+ | SELECT 1 AS myfield; | SELECT 1 AS myfield; test8 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); test7 | DROP FUNCTION foobar.foo(); | DROP FUNCTION foobar.foo(); (10 rows) ALTER VIEW foobar.fooview RENAME TO barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test8 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test7 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test6 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test5 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test4 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test3 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test2 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test1 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test8 | CREATE VIEW foobar.fooview AS +| CREATE VIEW foobar.fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; test7 | CREATE VIEW foobar.fooview AS +| CREATE VIEW foobar.fooview AS + | SELECT 1 AS myfield; | SELECT 1 AS myfield; (10 rows) DROP VIEW foobar.barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test8 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test7 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test6 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test5 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test4 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test3 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test2 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test1 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test8 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; test7 | ALTER VIEW foobar.fooview RENAME TO barview; | ALTER VIEW foobar.fooview RENAME TO barview; (10 rows) CREATE SEQUENCE foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test8 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test7 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test6 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test5 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test4 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test3 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test2 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test1 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test8 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; test7 | DROP VIEW foobar.barview; | DROP VIEW foobar.barview; (10 rows) ALTER SEQUENCE foobar.foo RESTART; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------+------------------------------------ test8 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test7 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test6 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test5 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test4 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test3 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test2 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test1 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test8 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; test7 | CREATE SEQUENCE foobar.foo; | CREATE SEQUENCE foobar.foo; (10 rows) DROP SEQUENCE foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------+------------------------------------ test8 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test7 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test6 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test5 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test4 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test3 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test2 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test1 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test8 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; test7 | ALTER SEQUENCE foobar.foo RESTART; | ALTER SEQUENCE foobar.foo RESTART; (10 rows) DROP SCHEMA foobar CASCADE; SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ (0 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test8 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test7 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test6 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test5 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test4 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test3 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test2 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test1 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test8 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; test7 | DROP SEQUENCE foobar.foo; | DROP SEQUENCE foobar.foo; (10 rows) SELECT * FROM pgl_ddl_deploy.unhandled; id | set_name | pid | executed_at | ddl_sql_raw | command_tag | reason | txid | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+-------------+-------------+--------+------+---------------+----------+---------------- (0 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/34_multi.out000066400000000000000000000476711453171545300205160ustar00rootroot00000000000000SET log_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA foobar; --This should never be allowed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test4 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test3 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test2 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test1 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test8 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test7 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+---------------------------------------------------------------------------------------------------------------------+--------------+----------------------- test8 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test7 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test6 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test5 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test4 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test3 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test2 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test1 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test4 | CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo; | CREATE TABLE | rejected_command_tags test3 | CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo; | CREATE TABLE | rejected_command_tags (10 rows) --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foo(id int primary key); COMMIT;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled COMMIT \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled COMMIT SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------------------+------------------------------------------------ test7 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test5 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test3 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test1 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test3 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE foo(id int primary key); test1 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE foo(id int primary key); test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+---------------------------------------------------------------------------------------------------------------------+--------------+-------------------------- test8 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test6 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test8 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test7 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test6 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test5 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags (10 rows) --Run all commands through cli to avoid permissions issues \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo CASCADE;" DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foobar.foo CASCADE;" DROP TABLE --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------------------------------------------------------+----------------------------------------------------------------------------- test7 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test5 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test3 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test1 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test3 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; test1 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+--------------+-------------------------- test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test8 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test6 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement (10 rows) \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key);" CREATE TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key);" CREATE TABLE --This should be allowed by some but not others \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo, foobar.foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test4 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test3 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test2 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test1 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test8 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test7 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test6 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test5 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test4 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test3 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+--------------+-------------------------- test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) --Resolutions SELECT pgl_ddl_deploy.resolve_unhandled(id, 'DBA superhero deployed it manually on the subscribers!') FROM pgl_ddl_deploy.unhandled; resolve_unhandled ------------------- t t t t t t t t t t t t t t t t t t t t t t t t t t t t (28 rows) --Test with no rows and a dummy row SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; resolve_exception ------------------- (0 rows) BEGIN; INSERT INTO pgl_ddl_deploy.exceptions (set_name) VALUES ('test1'); SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; resolve_exception ------------------- t (1 row) ROLLBACK; SELECT resolved, resolved_notes, set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; resolved | resolved_notes | set_name | ddl_sql_raw | command_tag | reason ----------+--------------------------------------------------------+----------+-----------------------------------------------------------------------------+--------------+-------------------------- t | DBA superhero deployed it manually on the subscribers! | test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/34_multi_1.out000066400000000000000000000500511453171545300207200ustar00rootroot00000000000000SET log_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA foobar; --This should never be allowed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled CREATE TABLE INSERT 0 3 DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled CREATE TABLE INSERT 0 3 DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------+----------------------------- test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test4 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test3 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test2 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test1 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test8 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; test7 | DROP SCHEMA foobar CASCADE; | DROP SCHEMA foobar CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+---------------------------------------------------------------------------------------------------------------------+--------------+----------------------- test8 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test7 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test6 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test5 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test4 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test3 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test2 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test1 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test4 | CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo; | CREATE TABLE | rejected_command_tags test3 | CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo; | CREATE TABLE | rejected_command_tags (10 rows) --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foo(id int primary key); COMMIT;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled BEGIN CREATE TABLE COMMIT \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled BEGIN CREATE TABLE COMMIT SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------------------+------------------------------------------------ test7 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test5 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test3 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test1 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE foobar.foo(id int primary key); test3 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE foo(id int primary key); test1 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE foo(id int primary key); test8 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test7 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test6 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; test5 | CREATE SCHEMA foobar; | CREATE SCHEMA foobar; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+---------------------------------------------------------------------------------------------------------------------+--------------+-------------------------- test8 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test6 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test8 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test7 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test6 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags test5 | CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo; | CREATE TABLE | rejected_command_tags (10 rows) --Run all commands through cli to avoid permissions issues \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo CASCADE;" DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foobar.foo CASCADE;" DROP TABLE --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled CREATE TABLE DROP TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled CREATE TABLE DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-----------------------------------------------------------------------------+----------------------------------------------------------------------------- test7 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test5 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test3 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test1 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; test3 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; test1 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+--------------+-------------------------- test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test8 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test6 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test4 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement test2 | BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT; | CREATE TABLE | rejected_multi_statement (10 rows) \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key);" CREATE TABLE \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key);" CREATE TABLE --This should be allowed by some but not others \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo, foobar.foo CASCADE;" WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled DROP TABLE SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test4 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test3 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test2 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test1 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE foo, foobar.foo CASCADE; test8 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test7 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test6 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test5 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test4 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); test3 | CREATE TABLE foobar.foo(id int primary key); | CREATE TABLE foobar.foo(id int primary key); (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+--------------+-------------------------- test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) --Resolutions SELECT pgl_ddl_deploy.resolve_unhandled(id, 'DBA superhero deployed it manually on the subscribers!') FROM pgl_ddl_deploy.unhandled; resolve_unhandled ------------------- t t t t t t t t t t t t t t t t t t t t t t t t t t t t (28 rows) --Test with no rows and a dummy row SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; resolve_exception ------------------- (0 rows) BEGIN; INSERT INTO pgl_ddl_deploy.exceptions (set_name) VALUES ('test1'); SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; resolve_exception ------------------- t (1 row) ROLLBACK; SELECT resolved, resolved_notes, set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; resolved | resolved_notes | set_name | ddl_sql_raw | command_tag | reason ----------+--------------------------------------------------------+----------+-----------------------------------------------------------------------------+--------------+-------------------------- t | DBA superhero deployed it manually on the subscribers! | test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects t | DBA superhero deployed it manually on the subscribers! | test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test4 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test2 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test4 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement t | DBA superhero deployed it manually on the subscribers! | test2 | CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/35_edges.out000066400000000000000000000041571453171545300204440ustar00rootroot00000000000000SET client_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY); CREATE TABLE foo (id SERIAL PRIMARY KEY); --This is an edge case that currently can't be dealt with well with filtered replication. ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------------------------------+------------------------------------------------------------------ test8 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test7 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test6 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test5 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test4 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test3 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test2 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test1 | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); | ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); test4 | CREATE TABLE foo (id SERIAL PRIMARY KEY); | CREATE TABLE foo (id SERIAL PRIMARY KEY); test3 | CREATE TABLE foo (id SERIAL PRIMARY KEY); | CREATE TABLE foo (id SERIAL PRIMARY KEY); (10 rows) DROP TABLE foobar.foo CASCADE; DROP TABLE foo CASCADE; pgl_ddl_deploy-2.2.1/expected/36_ignored.out000066400000000000000000000023241453171545300207770ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; CREATE TEMP TABLE foo(id SERIAL PRIMARY KEY); ALTER TABLE foo ADD COLUMN bla TEXT; DROP TABLE foo; SELECT 1 AS myfield INTO TEMP foo; DROP TABLE foo; CREATE TEMP TABLE foo AS SELECT 1 AS myfield; DROP TABLE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+--------------------------------+-------------------------------- test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) pgl_ddl_deploy-2.2.1/expected/37_unsupported.out000066400000000000000000000150231453171545300217410ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo AS SELECT 1 AS myfield; WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+--------------------------------+-------------------------------- test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+-----------------------------------------------------------------------------+-----------------+-------------------------- test4 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test3 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test2 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test1 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test8 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test7 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test6 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test5 | DROP TABLE foo, foobar.foo CASCADE; | DROP TABLE | mixed_objects test8 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement test6 | CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE; | CREATE TABLE | rejected_multi_statement (10 rows) SELECT 1 AS myfield INTO foobar.foo; WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled WARNING: Unhandled deployment logged in pgl_ddl_deploy.unhandled SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+--------------------------------+-------------------------------- test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (10 rows) SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | command_tag | reason ----------+--------------------------------------+-----------------+--------------------- test8 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test7 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test6 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test5 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test4 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test3 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test2 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test1 | SELECT 1 AS myfield INTO foobar.foo; | SELECT INTO | unsupported_command test4 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | test3 | CREATE TABLE foo AS +| CREATE TABLE AS | unsupported_command | SELECT 1 AS myfield; | | (10 rows) DROP TABLE foo; DROP TABLE foobar.foo; SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/38_no_create_user.out000066400000000000000000000003211453171545300223420ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE CREATE ROLE test_pgl_ddl_deploy_nopriv; SET ROLE test_pgl_ddl_deploy_nopriv; CREATE TEMP TABLE bla (id serial primary key); DROP TABLE bla; RESET ROLE; pgl_ddl_deploy-2.2.1/expected/39_override.out000066400000000000000000000020201453171545300211630ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE SET SESSION_REPLICATION_ROLE TO REPLICA; CREATE TABLE i_want_to_ignore_evts (id serial primary key); DROP TABLE i_want_to_ignore_evts; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------+------------------------ test8 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test7 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test6 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test5 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test4 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test3 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test2 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test1 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test4 | DROP TABLE foo; | DROP TABLE foo; test3 | DROP TABLE foo; | DROP TABLE foo; (10 rows) RESET SESSION_REPLICATION_ROLE; pgl_ddl_deploy-2.2.1/expected/40_sql_command_tags.out000066400000000000000000000011611453171545300226540ustar00rootroot00000000000000SELECT pgl_ddl_deploy.sql_command_tags(NULL); sql_command_tags ------------------ (1 row) SELECT pgl_ddl_deploy.sql_command_tags(''); ERROR: Invalid sql command SELECT pgl_ddl_deploy.sql_command_tags('CREATE EXTENSON foo;'); ERROR: syntax error at or near "EXTENSON" LINE 1: SELECT pgl_ddl_deploy.sql_command_tags('CREATE EXTENSON foo;... ^ SELECT pgl_ddl_deploy.sql_command_tags('CREATE TABLE foo(); ALTER TABLE foo ADD COLUMN bar text; DROP TABLE foo;'); sql_command_tags --------------------------------------------- {"CREATE TABLE","ALTER TABLE","DROP TABLE"} (1 row) pgl_ddl_deploy-2.2.1/expected/41_transaction.out000066400000000000000000000265111453171545300216750ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; SET client_min_messages TO warning; BEGIN; /*** In default schema **/ CREATE TABLE foo(id serial primary key); SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ test1 | foo test2 | foo test3 | foo test4 | foo (4 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test2 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test1 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test8 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test7 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test6 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test5 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test4 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test3 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; (10 rows) ALTER TABLE foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test2 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test1 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test8 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; test7 | DROP TABLE foobar.foo; | DROP TABLE foobar.foo; (10 rows) INSERT INTO foo (bla) VALUES (1),(2),(3); DROP TABLE foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+------------------------------------------+------------------------------------------ test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test2 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test4 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foo ADD COLUMN bla TEXT; | ALTER TABLE foo ADD COLUMN bla TEXT; test4 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); test3 | /*** +| /*** + | In default schema +| In default schema + | **/ +| **/ + | CREATE TABLE foo(id serial primary key); | CREATE TABLE foo(id serial primary key); (10 rows) CREATE TABLE foobar.foo(id serial primary key); SELECT * FROM check_rep_tables(); set_name | table_name ----------+------------ test1 | foobar.foo test2 | foobar.foo test3 | foobar.foo test4 | foobar.foo test5 | foobar.foo test6 | foobar.foo test7 | foobar.foo test8 | foobar.foo (8 rows) SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------+------------------------------------------------- test8 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test7 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test6 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test5 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test4 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test3 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test2 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test1 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test4 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test3 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; (10 rows) ALTER TABLE foobar.foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+-------------------------------------------------+------------------------------------------------- test8 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test7 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test6 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test5 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test4 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test3 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test2 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test1 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test8 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); test7 | CREATE TABLE foobar.foo(id serial primary key); | CREATE TABLE foobar.foo(id serial primary key); (10 rows) INSERT INTO foobar.foo (bla) VALUES (1),(2),(3); DROP TABLE foobar.foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------+--------------------------------------------- test8 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test7 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test6 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test5 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test4 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test3 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test2 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test1 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; test8 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; test7 | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; | ALTER TABLE foobar.foo ADD COLUMN bla TEXT; (10 rows) COMMIT; SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/43_new_set_behavior.out000066400000000000000000000123721453171545300226750ustar00rootroot00000000000000SET client_min_messages = warning; \set VERBOSITY terse --This should fail due to overlapping tags INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'test1', '.*', TRUE, TRUE, FALSE, '{"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"}', '{"DROP VIEW","DROP FUNCTION"}'; ERROR: You have overlapping configuration types and command tags which is not permitted: test1: include_schema_regex: ALTER FUNCTION, ALTER VIEW, CREATE FUNCTION, CREATE VIEW, DROP FUNCTION, DROP VIEW --But if we drop these tags from test1, it should work UPDATE pgl_ddl_deploy.set_configs SET create_tags = '{ALTER TABLE,CREATE SEQUENCE,ALTER SEQUENCE,CREATE SCHEMA,CREATE TABLE,CREATE TYPE,ALTER TYPE}', drop_tags = '{DROP SCHEMA,DROP TABLE,DROP TYPE,DROP SEQUENCE}' WHERE set_name = 'test1'; --Now this set will only handle these tags INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'test1', '.*', TRUE, TRUE, FALSE, '{"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"}', '{"DROP VIEW","DROP FUNCTION"}'; --include_only_repset_tables DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION my_special_tables_1; CREATE PUBLICATION my_special_tables_2;$sql$; ELSE CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('my_special_tables_1'::TEXT), ('my_special_tables_2'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; END IF; END$$; --Only ALTER TABLE makes sense (and is allowed) with include_only_repset_tables. So this should fail INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"CREATE TABLE"}'; ERROR: new row for relation "set_configs" violates check constraint "repset_tables_restricted_tags" --This is OK INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'temp_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; DELETE FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; --This also should fail - no DROP tags at all allowed INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', '{"DROP TABLE"}'; ERROR: new row for relation "set_configs" violates check constraint "repset_tables_restricted_tags" --These both are OK INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_2', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; --Check we get the defaults we want from the trigger BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex) VALUES ('temp_1', '.*'); SELECT create_tags, drop_tags FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; create_tags | drop_tags -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------- {"ALTER TABLE","CREATE SEQUENCE","ALTER SEQUENCE","CREATE SCHEMA","CREATE TABLE","CREATE FUNCTION","ALTER FUNCTION","CREATE TYPE","ALTER TYPE","CREATE VIEW","ALTER VIEW",COMMENT,"CREATE RULE","CREATE TRIGGER","ALTER TRIGGER"} | {"DROP SCHEMA","DROP TABLE","DROP FUNCTION","DROP TYPE","DROP VIEW","DROP SEQUENCE"} (1 row) ROLLBACK; BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_only_repset_tables) VALUES ('temp_1', TRUE); SELECT create_tags, drop_tags FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; create_tags | drop_tags -----------------+----------- {"ALTER TABLE"} | (1 row) ROLLBACK; --Now deploy again separately --By set_name: SELECT pgl_ddl_deploy.deploy('test1'); deploy -------- t (1 row) --By set_config_id SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; deploy -------- t t (2 rows) pgl_ddl_deploy-2.2.1/expected/44_multi_set_tags.out000066400000000000000000000072241453171545300223760ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA viewer; --Should be handled by separate set_config CREATE TABLE viewer.foo(id int primary key); --Should be handled by separate set_config CREATE VIEW viewer.vw_foo AS SELECT * FROM viewer.foo; SELECT c.create_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name = 'test1' ORDER BY e.id DESC LIMIT 4; create_tags | set_name | ddl_sql_raw | ddl_sql_sent --------------------------------------------------------------------------------------------------------------+----------+----------------------------------------------+---------------------------------------------- {"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"} | test1 | CREATE VIEW viewer.vw_foo AS +| CREATE VIEW viewer.vw_foo AS + | | SELECT * FROM viewer.foo; | SELECT * FROM viewer.foo; {"ALTER TABLE","CREATE SEQUENCE","ALTER SEQUENCE","CREATE SCHEMA","CREATE TABLE","CREATE TYPE","ALTER TYPE"} | test1 | CREATE TABLE viewer.foo(id int primary key); | CREATE TABLE viewer.foo(id int primary key); {"ALTER TABLE","CREATE SEQUENCE","ALTER SEQUENCE","CREATE SCHEMA","CREATE TABLE","CREATE TYPE","ALTER TYPE"} | test1 | CREATE SCHEMA viewer; | CREATE SCHEMA viewer; {"ALTER TABLE","CREATE SEQUENCE","ALTER SEQUENCE","CREATE SCHEMA","CREATE TABLE","CREATE TYPE","ALTER TYPE"} | test1 | DROP TABLE foobar.foo CASCADE; | DROP TABLE foobar.foo CASCADE; (4 rows) DROP VIEW viewer.vw_foo; DROP TABLE viewer.foo CASCADE; DROP SCHEMA viewer; SELECT c.drop_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name = 'test1' ORDER BY e.id DESC LIMIT 4; drop_tags | set_name | ddl_sql_raw | ddl_sql_sent ----------------------------------------------------------+----------+--------------------------------+-------------------------------- {"DROP SCHEMA","DROP TABLE","DROP TYPE","DROP SEQUENCE"} | test1 | DROP SCHEMA viewer; | DROP SCHEMA viewer; {"DROP SCHEMA","DROP TABLE","DROP TYPE","DROP SEQUENCE"} | test1 | DROP TABLE viewer.foo CASCADE; | DROP TABLE viewer.foo CASCADE; {"DROP VIEW","DROP FUNCTION"} | test1 | DROP VIEW viewer.vw_foo; | DROP VIEW viewer.vw_foo; {"DROP VIEW","DROP FUNCTION"} | test1 | CREATE VIEW viewer.vw_foo AS +| CREATE VIEW viewer.vw_foo AS + | | SELECT * FROM viewer.foo; | SELECT * FROM viewer.foo; (4 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN SELECT COUNT(1) INTO v_ct FROM pg_publication_tables WHERE schemaname = 'pgl_ddl_deploy' AND tablename = 'queue'; RAISE LOG 'v_ct: %', v_ct; PERFORM verify_count(v_ct, 8); END IF; END$$; pgl_ddl_deploy-2.2.1/expected/45_include_only_repset_tables_1.out000066400000000000000000000006051453171545300251700ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; --These kinds of repsets will not replicate CREATE events, only ALTER TABLE, so deploy after CREATE --We assume schema will be copied to subscriber separately CREATE SCHEMA special; CREATE TABLE special.foo (id serial primary key, foo text, bar text); CREATE TABLE special.bar (id serial primary key, super text, man text); pgl_ddl_deploy-2.2.1/expected/46_include_only_repset_tables_2.out000066400000000000000000000026151453171545300251750ustar00rootroot00000000000000SET client_min_messages = warning; SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.foo'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_1'; add_table_to_replication -------------------------- t (1 row) SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.bar'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_2'; add_table_to_replication -------------------------- t (1 row) --Deploy by set_name SELECT pgl_ddl_deploy.deploy('my_special_tables_1'); deploy -------- t (1 row) SELECT pgl_ddl_deploy.deploy('my_special_tables_2'); deploy -------- t (1 row) --Ensure these kinds of configs only have 'create' event triggers SELECT COUNT(1) FROM pg_event_trigger evt INNER JOIN pgl_ddl_deploy.event_trigger_schema ets ON evt.evtname IN(auto_replication_unsupported_trigger_name, ets.auto_replication_drop_trigger_name, ets.auto_replication_create_trigger_name) WHERE include_only_repset_tables; count ------- 2 (1 row) --Deploy by id SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'my_special_tables_1'; deploy -------- t (1 row) SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'my_special_tables_2'; deploy -------- t (1 row) pgl_ddl_deploy-2.2.1/expected/47_include_only_repset_tables_3.out000066400000000000000000000060621453171545300251770ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; ALTER TABLE special.foo ADD COLUMN happy TEXT; ALTER TABLE special.bar ADD COLUMN happier TEXT; SELECT c.create_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; create_tags | set_name | ddl_sql_raw | ddl_sql_sent -----------------+---------------------+--------------------------------------------------+-------------------------------------------------- {"ALTER TABLE"} | my_special_tables_2 | ALTER TABLE special.bar ADD COLUMN happier TEXT; | ALTER TABLE special.bar ADD COLUMN happier TEXT; {"ALTER TABLE"} | my_special_tables_1 | ALTER TABLE special.foo ADD COLUMN happy TEXT; | ALTER TABLE special.foo ADD COLUMN happy TEXT; (2 rows) --Test renaming which was missing in 1.2 ALTER TABLE special.foo RENAME COLUMN happy to happyz; ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz; ALTER TABLE special.foo RENAME COLUMN id TO id_2; ALTER TABLE special.bar RENAME COLUMN happier TO happierz; ALTER TABLE special.bar RENAME COLUMN id TO id_3; ALTER TABLE special.foo RENAME TO fooz; ALTER TABLE special.bar RENAME TO barz; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 20; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+------------------------------------------------------------+------------------------------------------------------------ my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; my_special_tables_2 | ALTER TABLE special.bar ADD COLUMN happier TEXT; | ALTER TABLE special.bar ADD COLUMN happier TEXT; my_special_tables_1 | ALTER TABLE special.foo ADD COLUMN happy TEXT; | ALTER TABLE special.foo ADD COLUMN happy TEXT; (9 rows) pgl_ddl_deploy-2.2.1/expected/48_include_only_repset_tables_4.out000066400000000000000000000131451453171545300252010ustar00rootroot00000000000000SET client_min_messages = warning; CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$ BEGIN RETURN NULL; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER noop BEFORE DELETE ON special.fooz FOR EACH ROW EXECUTE PROCEDURE noop(); ALTER TABLE special.fooz DISABLE TRIGGER noop; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+------------------------------------------------------------+------------------------------------------------------------ my_special_tables_1 | ALTER TABLE special.fooz DISABLE TRIGGER noop; | ALTER TABLE special.fooz DISABLE TRIGGER noop; my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; my_special_tables_2 | ALTER TABLE special.bar ADD COLUMN happier TEXT; | ALTER TABLE special.bar ADD COLUMN happier TEXT; my_special_tables_1 | ALTER TABLE special.foo ADD COLUMN happy TEXT; | ALTER TABLE special.foo ADD COLUMN happy TEXT; (10 rows) -- Test new subcommand functionality UPDATE pgl_ddl_deploy.set_configs SET exclude_alter_table_subcommands = pgl_ddl_deploy.common_exclude_alter_table_subcommands() WHERE include_only_repset_tables; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE include_only_repset_tables; deploy -------- t t (2 rows) SET client_min_messages = log; -- This should be ignored ALTER TABLE special.fooz ENABLE TRIGGER noop; LOG: Not processing DDL due to excluded subcommand(s): ENABLE TRIGGER: ALTER TABLE special.fooz ENABLE TRIGGER noop; -- This contains a tag we want to ignore but we can't separate out the parts - see the warning message ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); WARNING: Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: ADD CONSTRAINT, SQL: ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); ALTER TABLE special.fooz ADD COLUMN bar_id INT; -- This one should be ignored as well ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); LOG: Not processing DDL due to excluded subcommand(s): ADD CONSTRAINT: ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+--------------------------------------------------------------------------------+-------------------------------------------------------------------------------- my_special_tables_1 | ALTER TABLE special.fooz ADD COLUMN bar_id INT; | ALTER TABLE special.fooz ADD COLUMN bar_id INT; my_special_tables_2 | ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); | ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); my_special_tables_1 | ALTER TABLE special.fooz DISABLE TRIGGER noop; | ALTER TABLE special.fooz DISABLE TRIGGER noop; my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; (10 rows) SET client_min_messages = warning; DROP TABLE special.fooz CASCADE; DROP TABLE special.barz CASCADE; DROP SCHEMA special; pgl_ddl_deploy-2.2.1/expected/48_include_only_repset_tables_4_1.out000066400000000000000000000132011453171545300254120ustar00rootroot00000000000000SET client_min_messages = warning; CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$ BEGIN RETURN NULL; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER noop BEFORE DELETE ON special.fooz FOR EACH ROW EXECUTE PROCEDURE noop(); ALTER TABLE special.fooz DISABLE TRIGGER noop; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+------------------------------------------------------------+------------------------------------------------------------ my_special_tables_1 | ALTER TABLE special.fooz DISABLE TRIGGER noop; | ALTER TABLE special.fooz DISABLE TRIGGER noop; my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; my_special_tables_2 | ALTER TABLE special.bar ADD COLUMN happier TEXT; | ALTER TABLE special.bar ADD COLUMN happier TEXT; my_special_tables_1 | ALTER TABLE special.foo ADD COLUMN happy TEXT; | ALTER TABLE special.foo ADD COLUMN happy TEXT; (10 rows) -- Test new subcommand functionality UPDATE pgl_ddl_deploy.set_configs SET exclude_alter_table_subcommands = pgl_ddl_deploy.common_exclude_alter_table_subcommands() WHERE include_only_repset_tables; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE include_only_repset_tables; deploy -------- t t (2 rows) SET client_min_messages = log; -- This should be ignored ALTER TABLE special.fooz ENABLE TRIGGER noop; LOG: Not processing DDL due to excluded subcommand(s): ENABLE TRIGGER: ALTER TABLE special.fooz ENABLE TRIGGER noop; -- This contains a tag we want to ignore but we can't separate out the parts - see the warning message ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); WARNING: Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: ADD CONSTRAINT (and recurse), SQL: ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); ALTER TABLE special.fooz ADD COLUMN bar_id INT; -- This one should be ignored as well ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); LOG: Not processing DDL due to excluded subcommand(s): ADD CONSTRAINT (and recurse): ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ---------------------+--------------------------------------------------------------------------------+-------------------------------------------------------------------------------- my_special_tables_1 | ALTER TABLE special.fooz ADD COLUMN bar_id INT; | ALTER TABLE special.fooz ADD COLUMN bar_id INT; my_special_tables_2 | ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); | ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); my_special_tables_1 | ALTER TABLE special.fooz DISABLE TRIGGER noop; | ALTER TABLE special.fooz DISABLE TRIGGER noop; my_special_tables_2 | ALTER TABLE special.bar RENAME TO barz; | ALTER TABLE special.bar RENAME TO barz; my_special_tables_1 | ALTER TABLE special.foo RENAME TO fooz; | ALTER TABLE special.foo RENAME TO fooz; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN id TO id_3; | ALTER TABLE special.bar RENAME COLUMN id TO id_3; my_special_tables_2 | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; | ALTER TABLE special.bar RENAME COLUMN happier TO happierz; my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN id TO id_2; | ALTER TABLE special.foo RENAME COLUMN id TO id_2; my_special_tables_1 | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); | ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); my_special_tables_1 | ALTER TABLE special.foo RENAME COLUMN happy to happyz; | ALTER TABLE special.foo RENAME COLUMN happy to happyz; (10 rows) SET client_min_messages = warning; DROP TABLE special.fooz CASCADE; DROP TABLE special.barz CASCADE; DROP SCHEMA special; pgl_ddl_deploy-2.2.1/expected/49_unprivileged_users.out000066400000000000000000000001411453171545300232650ustar00rootroot00000000000000CREATE ROLE unpriv; SET ROLE unpriv; CREATE TEMP TABLE foo(); ALTER TABLE foo ADD COLUMN id INT; pgl_ddl_deploy-2.2.1/expected/50_is_deployed.out000066400000000000000000000100661453171545300216460ustar00rootroot00000000000000SET client_min_messages TO warning; --Test what is_deployed shows (introduced in 1.3) SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; set_name | is_deployed ---------------------+------------- test1 | t test2 | t test3 | t test4 | t test5 | t test6 | t test7 | t test8 | t test1 | t my_special_tables_1 | t my_special_tables_2 | t (11 rows) SELECT pgl_ddl_deploy.undeploy(id) FROM pgl_ddl_deploy.set_configs; undeploy ---------- t t t t t t t t t t t (11 rows) SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; set_name | is_deployed ---------------------+------------- test1 | f test2 | f test3 | f test4 | f test5 | f test6 | f test7 | f test8 | f test1 | f my_special_tables_1 | f my_special_tables_2 | f (11 rows) --Nothing should replicate this CREATE TABLE foobar (id serial primary key); DROP TABLE foobar; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------+---------------------------------- test4 | DROP SCHEMA special; | DROP SCHEMA special; test3 | DROP SCHEMA special; | DROP SCHEMA special; test2 | DROP SCHEMA special; | DROP SCHEMA special; test1 | DROP SCHEMA special; | DROP SCHEMA special; test4 | DROP TABLE special.barz CASCADE; | DROP TABLE special.barz CASCADE; test3 | DROP TABLE special.barz CASCADE; | DROP TABLE special.barz CASCADE; test2 | DROP TABLE special.barz CASCADE; | DROP TABLE special.barz CASCADE; test1 | DROP TABLE special.barz CASCADE; | DROP TABLE special.barz CASCADE; test4 | DROP TABLE special.fooz CASCADE; | DROP TABLE special.fooz CASCADE; test3 | DROP TABLE special.fooz CASCADE; | DROP TABLE special.fooz CASCADE; (10 rows) --Re-deploy and check again what shows as deployed SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs; deploy -------- t t t t t t t t t t t (11 rows) SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; set_name | is_deployed ---------------------+------------- test1 | t test2 | t test3 | t test4 | t test5 | t test6 | t test7 | t test8 | t test1 | t my_special_tables_1 | t my_special_tables_2 | t (11 rows) CREATE TABLE foobar (id serial primary key); DROP TABLE foobar CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+----------------------------------------------+---------------------------------------------- test4 | DROP TABLE foobar CASCADE; | DROP TABLE foobar CASCADE; test3 | DROP TABLE foobar CASCADE; | DROP TABLE foobar CASCADE; test2 | DROP TABLE foobar CASCADE; | DROP TABLE foobar CASCADE; test1 | DROP TABLE foobar CASCADE; | DROP TABLE foobar CASCADE; test4 | CREATE TABLE foobar (id serial primary key); | CREATE TABLE foobar (id serial primary key); test3 | CREATE TABLE foobar (id serial primary key); | CREATE TABLE foobar (id serial primary key); test2 | CREATE TABLE foobar (id serial primary key); | CREATE TABLE foobar (id serial primary key); test1 | CREATE TABLE foobar (id serial primary key); | CREATE TABLE foobar (id serial primary key); test4 | DROP SCHEMA special; | DROP SCHEMA special; test3 | DROP SCHEMA special; | DROP SCHEMA special; (10 rows) pgl_ddl_deploy-2.2.1/expected/51_1_4_features.out000066400000000000000000000067031453171545300216330ustar00rootroot00000000000000SET client_min_messages = warning; -- We need to eventually test this on a real subscriber SET search_path TO ''; CREATE SCHEMA bla; -- We test the subcommand feature with the other repset_table tests SELECT pgl_ddl_deploy.undeploy(id) FROM pgl_ddl_deploy.set_configs; undeploy ---------- t t t t t t t t t t t (11 rows) DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION test_ddl_only;$sql$; ELSE CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('test_ddl_only'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; END IF; END$$; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, ddl_only_replication) VALUES ('test_ddl_only','^super.*',false); -- It is now permitted to have multiple set_configs for same set_name if using ddl_only_replication INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, ddl_only_replication) VALUES ('test_ddl_only','^duper.*',true); SET ROLE postgres; SELECT pgl_ddl_deploy.deploy('test_ddl_only'); deploy -------- t (1 row) -- The difference here is that the latter table is under ddl_only_replication SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA super; CREATE TABLE super.man(id serial primary key); CREATE SCHEMA duper; CREATE TABLE duper.man(id serial primary key); -- Now assume we just want to replicate structure going forward ONLY ALTER TABLE super.man ADD COLUMN foo text; ALTER TABLE duper.man ADD COLUMN foo text; -- No cascade required for second drop because it was not added to replication DROP TABLE super.man CASCADE; DROP TABLE duper.man; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.ddl_only_replication FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | ddl_only_replication ---------------+------------------------------------------------+------------------------------------------------+---------------------- test_ddl_only | DROP TABLE duper.man; | DROP TABLE duper.man; | t test_ddl_only | DROP TABLE super.man CASCADE; | DROP TABLE super.man CASCADE; | f test_ddl_only | ALTER TABLE duper.man ADD COLUMN foo text; | ALTER TABLE duper.man ADD COLUMN foo text; | t test_ddl_only | ALTER TABLE super.man ADD COLUMN foo text; | ALTER TABLE super.man ADD COLUMN foo text; | f test_ddl_only | CREATE TABLE duper.man(id serial primary key); | CREATE TABLE duper.man(id serial primary key); | t test_ddl_only | CREATE SCHEMA duper; | CREATE SCHEMA duper; | t test_ddl_only | CREATE TABLE super.man(id serial primary key); | CREATE TABLE super.man(id serial primary key); | f test_ddl_only | CREATE SCHEMA super; | CREATE SCHEMA super; | f test4 | CREATE SCHEMA bla; | CREATE SCHEMA bla; | f test3 | CREATE SCHEMA bla; | CREATE SCHEMA bla; | f (10 rows) pgl_ddl_deploy-2.2.1/expected/52_sub_retries.out000066400000000000000000014711611453171545300217060ustar00rootroot00000000000000--****NOTE*** this file drops the whole extension and all previous test setup. --If adding new tests, it is best to keep this file as the last test before cleanup. SET client_min_messages = warning; SELECT pubnames, message_type, regexp_replace(regexp_replace(regexp_replace(message::text, 'p_pid := (\d+)', 'p_pid := ?'), 'p_provider_name := (NULL|''\w+'')', 'p_provider_name := ?'), 'p_driver := (''\w+'')', 'p_driver := ?') as message FROM all_queues() WHERE NOT message::text LIKE '%notify_subscription_refresh%' ORDER BY queued_at; pubnames | message_type | message -----------------------+--------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n /***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n /***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE FUNCTION foobar.foo() RETURNS INT AS\n$BODY$\nSELECT 1;\n$BODY$\nLANGUAGE SQL STABLE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foobar.foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foobar.foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foobar.foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foobar.foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foobar.foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER FUNCTION foobar.foo() OWNER TO current_user;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foobar.foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER FUNCTION foobar.foo() OWNER TO current_user;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER FUNCTION foobar.foo() OWNER TO current_user;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foobar.foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foobar.foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foobar.foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foobar.foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foobar.foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP FUNCTION foobar.foo();$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foobar.foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP FUNCTION foobar.foo();$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP FUNCTION foobar.foo();\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'fooview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW foobar.fooview AS\nSELECT 1 AS myfield;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW foobar.fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW foobar.fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW foobar.fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW foobar.fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW foobar.fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER VIEW foobar.fooview RENAME TO barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW foobar.fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'barview',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER VIEW foobar.fooview RENAME TO barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER VIEW foobar.fooview RENAME TO barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW foobar.barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW foobar.barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW foobar.barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW foobar.barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW foobar.barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW foobar.barview;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW foobar.barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW foobar.barview;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW foobar.barview;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foobar.foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foobar.foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foobar.foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foobar.foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foobar.foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER SEQUENCE foobar.foo RESTART;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foobar.foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER SEQUENCE foobar.foo RESTART;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER SEQUENCE foobar.foo RESTART;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SEQUENCE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SEQUENCE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SEQUENCE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA foobar;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA foobar;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA foobar;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ CREATE TABLE foo(id int primary key); $PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo(id int primary key); \n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foobar.foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ CREATE TABLE foobar.foo(id int primary key); $PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foobar.foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key); \n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foobar.foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ CREATE TABLE foobar.foo(id int primary key); $PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ CREATE TABLE foobar.foo(id int primary key); $pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key); \n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo, foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo, foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo, foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo, foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo, foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo, foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo, foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo, foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foo (id SERIAL PRIMARY KEY);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foo (id SERIAL PRIMARY KEY);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foo (id SERIAL PRIMARY KEY);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n /***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$/***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n /***\nIn default schema\n**/\nCREATE TABLE foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar.foo(id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar.foo(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar.foo(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := 'foobar',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE foobar.foo ADD COLUMN bla TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE foobar.foo ADD COLUMN bla TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test5} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test5'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 5,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test6} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test6'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 6,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test7} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test7'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 7,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test8} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test8'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 8,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA viewer;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA viewer;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA viewer;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA viewer;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'viewer',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE viewer.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE viewer.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'viewer',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE viewer.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE viewer.foo(id int primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'viewer',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE viewer.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE viewer.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'viewer',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE viewer.foo(id int primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE viewer.foo(id int primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'viewer',\n p_relname := 'vw_foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 11,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'viewer',\n p_relname := 'vw_foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'viewer',\n p_relname := 'vw_foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'viewer',\n p_relname := 'vw_foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE VIEW viewer.vw_foo AS\nSELECT * FROM viewer.foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW viewer.vw_foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW viewer.vw_foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 11,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW viewer.vw_foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP VIEW viewer.vw_foo;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW viewer.vw_foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW viewer.vw_foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP VIEW viewer.vw_foo;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP VIEW viewer.vw_foo;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE viewer.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE viewer.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE viewer.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE viewer.foo CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE viewer.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE viewer.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE viewer.foo CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE viewer.foo CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA viewer;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA viewer;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA viewer;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA viewer;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA viewer;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA special;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA special;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA special;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE SCHEMA special;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE special.foo (id serial primary key, foo text, bar text);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.foo (id serial primary key, foo text, bar text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE special.foo (id serial primary key, foo text, bar text);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'bar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.bar (id serial primary key, super text, man text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE special.bar (id serial primary key, super text, man text);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'bar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.bar (id serial primary key, super text, man text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE special.bar (id serial primary key, super text, man text);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'bar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.bar (id serial primary key, super text, man text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE special.bar (id serial primary key, super text, man text);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'bar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE special.bar (id serial primary key, super text, man text);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE special.bar (id serial primary key, super text, man text);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD COLUMN happy TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD COLUMN happy TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD COLUMN happy TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo ADD COLUMN happy TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD COLUMN happy TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo ADD COLUMN happy TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar ADD COLUMN happier TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar ADD COLUMN happier TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar ADD COLUMN happier TEXT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar ADD COLUMN happier TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar ADD COLUMN happier TEXT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar ADD COLUMN happier TEXT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME COLUMN happy to happyz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN happy to happyz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME COLUMN happy to happyz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME COLUMN id TO id_2;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'foo',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME COLUMN id TO id_2;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME COLUMN id TO id_2;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME COLUMN happier TO happierz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN happier TO happierz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME COLUMN happier TO happierz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME COLUMN id TO id_3;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'bar',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME COLUMN id TO id_3;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME COLUMN id TO id_3;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME TO fooz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME TO fooz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.foo RENAME TO fooz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME TO fooz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.foo RENAME TO fooz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.foo RENAME TO fooz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME TO barz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME TO barz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.bar RENAME TO barz;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME TO barz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.bar RENAME TO barz;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.bar RENAME TO barz;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 11,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$\nBEGIN\nRETURN NULL;\nEND;\n$BODY$\nLANGUAGE plpgsql;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz DISABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz DISABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz DISABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz DISABLE TRIGGER noop;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz DISABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz DISABLE TRIGGER noop;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ENABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ENABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ENABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ENABLE TRIGGER noop;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ENABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ENABLE TRIGGER noop;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ENABLE TRIGGER noop;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ENABLE TRIGGER noop;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_2'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 16,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'barz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {my_special_tables_1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['my_special_tables_1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 15,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ADD COLUMN bar_id INT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD COLUMN bar_id INT;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ADD COLUMN bar_id INT;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'special',\n p_relname := 'fooz',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.fooz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE special.fooz CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.fooz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE special.fooz CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.fooz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE special.fooz CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.fooz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE special.fooz CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.barz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE special.barz CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.barz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE special.barz CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.barz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE special.barz CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE special.barz CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE special.barz CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA special;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP SCHEMA special;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA special;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP SCHEMA special;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP SCHEMA special;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := 'public',\n p_relname := 'foobar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar (id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar (id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := 'public',\n p_relname := 'foobar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar (id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE TABLE foobar (id serial primary key);$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := 'public',\n p_relname := 'foobar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar (id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar (id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := 'public',\n p_relname := 'foobar_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE foobar (id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n CREATE TABLE foobar (id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$DROP TABLE foobar CASCADE;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE foobar CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO \"$user\", public;\n\n DROP TABLE foobar CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test1} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test1'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA bla;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA bla;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 1,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test2} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test2'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA bla;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$CREATE SCHEMA bla;$PGL_DDL_DEPLOY$);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 2,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test3} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test3'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA bla;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE SCHEMA bla;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 3,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test4} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test4'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA bla;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE SCHEMA bla;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 4,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA super;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE SCHEMA super;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 19,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := 'super',\n p_relname := 'man_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE super.man(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE TABLE super.man(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 19,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE SCHEMA duper;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE SCHEMA duper;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 20,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := 'duper',\n p_relname := 'man_pkey',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$CREATE TABLE duper.man(id serial primary key);$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n CREATE TABLE duper.man(id serial primary key);\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 20,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := 'super',\n p_relname := 'man',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE super.man ADD COLUMN foo text;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n ALTER TABLE super.man ADD COLUMN foo text;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 19,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := 'duper',\n p_relname := 'man',\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE duper.man ADD COLUMN foo text;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n ALTER TABLE duper.man ADD COLUMN foo text;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 20,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE super.man CASCADE;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n DROP TABLE super.man CASCADE;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 19,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " {test_ddl_only} | Q | "\n SELECT pgl_ddl_deploy.subscriber_command\n (\n p_provider_name := ?,\n p_set_name := ARRAY['test_ddl_only'],\n p_nspname := NULL,\n p_relname := NULL,\n p_ddl_sql_sent := $pgl_ddl_deploy_sql$DROP TABLE duper.man;$pgl_ddl_deploy_sql$,\n p_full_ddl := $pgl_ddl_deploy_sql$\n --Be sure to use provider's search_path for SQL environment consistency\n SET SEARCH_PATH TO '';\n\n DROP TABLE duper.man;\n ;\n $pgl_ddl_deploy_sql$,\n p_pid := ?,\n p_set_config_id := 20,\n p_queue_subscriber_failures := false,\n p_signal_blocking_subscriber_sessions := NULL,\n p_lock_timeout := 3000,\n p_driver := ?\n );\n " (432 rows) DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 AND NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN SELECT COUNT(1) INTO v_ct FROM all_queues() WHERE message::text LIKE '%notify_subscription_refresh%'; IF v_ct != 79 THEN RAISE EXCEPTION '%', v_ct; END IF; END IF; END$$; --Some day, we should regress with multiple databases. There are examples of this in pglogical code base --For now, we will mock the subscriber behavior, which is less than ideal, because it misses testing execution --on subscriber DROP OWNED BY test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy_nopriv; DROP EXTENSION pgl_ddl_deploy CASCADE; CREATE EXTENSION pgl_ddl_deploy; SELECT set_driver(); set_driver ------------ (1 row) SET SESSION_REPLICATION_ROLE TO REPLICA; --To ensure testing subscriber behavior CREATE ROLE test_pgl_ddl_deploy; GRANT CREATE ON DATABASE contrib_regression TO test_pgl_ddl_deploy; SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname = 'test_pgl_ddl_deploy'; add_role ---------- t (1 row) SET ROLE test_pgl_ddl_deploy; --Mock subscriber_log insert which should take place on subscriber error when option enabled INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 100, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW joy AS SELECT * FROM joyous', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous;', FALSE, 'relation "joyous" does not exist'); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {f} (1 row) SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; retry_subscriber_log ---------------------- f (1 row) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+-----------------------------------------+------------------------------------------------------------------------+-----------+---------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist (3 rows) CREATE TABLE joyous (id int); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {t} (1 row) SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- (1 row) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+-----------------------------------------+------------------------------------------------------------------------+-----------+---------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 4 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 4 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | t | (4 rows) --Now let's do 2 INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 101, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW happy AS SELECT * FROM happier;', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier;', FALSE, 'relation "happier" does not exist'); INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 102, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW glee AS SELECT * FROM gleeful;', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful;', FALSE, 'relation "gleeful" does not exist'); --The first fails and the second therefore is not attempted SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {f} (1 row) --Both fail if we try each separately SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; retry_subscriber_log ---------------------- f f (2 rows) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+---------------------------------------------+---------------------------------------------------------------------------+-----------+----------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 4 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 4 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | t | 5 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 7 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 6 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 9 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 7 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 8 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 8 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 9 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist (9 rows) --One succeeds, one fails CREATE TABLE happier (id int); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {t,f} (1 row) --One fails SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- {f} (1 row) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+---------------------------------------------+---------------------------------------------------------------------------+-----------+----------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 4 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 4 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | t | 5 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 7 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 6 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 9 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 7 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 8 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 8 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 10 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 9 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 11 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 10 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | t | 11 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 12 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 12 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist (12 rows) --Succeed with new id CREATE TABLE gleeful (id int); SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; retry_subscriber_log ---------------------- t (1 row) --Nothing SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; retry_subscriber_log ---------------------- (0 rows) SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); retry_all_subscriber_logs --------------------------- (1 row) SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; id | set_name | provider_pid | provider_node_name | provider_set_config_id | executed_as_role | origin_subscriber_log_id | next_subscriber_log_id | ddl_sql | full_ddl_sql | succeeded | error_message ----+----------+--------------+--------------------+------------------------+---------------------+--------------------------+------------------------+---------------------------------------------+---------------------------------------------------------------------------+-----------+----------------------------------- 1 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 2 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 2 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 3 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 3 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | 4 | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | f | relation "joyous" does not exist 4 | foo | 100 | awesome | 1 | test_pgl_ddl_deploy | 1 | | CREATE VIEW joy AS SELECT * FROM joyous | SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous; | t | 5 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 7 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 6 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 9 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 7 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 8 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 8 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | 10 | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | f | relation "happier" does not exist 9 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 11 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 10 | foo | 101 | awesome | 1 | test_pgl_ddl_deploy | 5 | | CREATE VIEW happy AS SELECT * FROM happier; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier; | t | 11 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 12 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 12 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | 13 | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | f | relation "gleeful" does not exist 13 | foo | 102 | awesome | 1 | test_pgl_ddl_deploy | 6 | | CREATE VIEW glee AS SELECT * FROM gleeful; | SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful; | t | (13 rows) DROP TABLE joyous CASCADE; DROP TABLE happier CASCADE; DROP TABLE gleeful CASCADE; pgl_ddl_deploy-2.2.1/expected/53_1_5_features.out000066400000000000000000000413431453171545300216350ustar00rootroot00000000000000-- Suppress pid-specific warning messages SET client_min_messages TO error; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test1','.*',true, true); -- It's generally good to use queue_subscriber_failures with include_everything, so a bogus grant won't break replication on subscriber INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_everything, queue_subscriber_failures, create_tags) VALUES ('test1',true, true, '{GRANT,REVOKE}'); SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; deploy -------- t t (2 rows) DISCARD TEMP; SET search_path TO public; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key, bla int); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------------+--------------------------------------------------- test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); (1 row) GRANT SELECT ON foo TO PUBLIC; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | include_everything ----------+---------------------------------------------------+---------------------------------------------------+-------------------- test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f (2 rows) INSERT INTO foo (bla) VALUES (1),(2),(3); REVOKE INSERT ON foo FROM PUBLIC; DROP TABLE foo CASCADE; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | include_everything ----------+---------------------------------------------------+---------------------------------------------------+-------------------- test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; | f test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; | t test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f (4 rows) SELECT * FROM pgl_ddl_deploy.unhandled; id | set_name | pid | executed_at | ddl_sql_raw | command_tag | reason | txid | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+-------------+-------------+--------+------+---------------+----------+---------------- (0 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) /***** Test cancel and terminate blocker functionality *****/ SET ROLE postgres; UPDATE pgl_ddl_deploy.set_configs SET lock_safe_deployment = FALSE, signal_blocking_subscriber_sessions = 'cancel'; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; deploy -------- t t (2 rows) SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key, bla int); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent ----------+---------------------------------------------------+--------------------------------------------------- test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); (5 rows) GRANT SELECT ON foo TO PUBLIC; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | include_everything ----------+---------------------------------------------------+---------------------------------------------------+-------------------- test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; | f test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; | t test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f (6 rows) INSERT INTO foo (bla) VALUES (1),(2),(3); REVOKE INSERT ON foo FROM PUBLIC; DROP TABLE foo CASCADE; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; set_name | ddl_sql_raw | ddl_sql_sent | include_everything ----------+---------------------------------------------------+---------------------------------------------------+-------------------- test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; | f test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; | t test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f test1 | DROP TABLE foo CASCADE; | DROP TABLE foo CASCADE; | f test1 | REVOKE INSERT ON foo FROM PUBLIC; | REVOKE INSERT ON foo FROM PUBLIC; | t test1 | GRANT SELECT ON foo TO PUBLIC; | GRANT SELECT ON foo TO PUBLIC; | t test1 | CREATE TABLE foo(id serial primary key, bla int); | CREATE TABLE foo(id serial primary key, bla int); | f (8 rows) SELECT * FROM pgl_ddl_deploy.unhandled; id | set_name | pid | executed_at | ddl_sql_raw | command_tag | reason | txid | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+-------------+-------------+--------+------+---------------+----------+---------------- (0 rows) SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) CREATE TABLE public.foo(id serial primary key, bla int); CREATE TABLE public.foo2 () INHERITS (public.foo); CREATE TABLE public.bar(id serial primary key, bla int); \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('cancel','public','foo'); signal | successful | state | query | reported | pg_sleep --------+------------+--------+-------------------------------------------------------+----------+---------- cancel | t | active | BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30); | f | (1 row) \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('terminate','public','foo'); signal | successful | state | query | reported | pg_sleep -----------+------------+--------+-------------------------------------------------------+----------+---------- terminate | t | active | BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30); | f | (1 row) \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & -- This process should not be killed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; INSERT INTO public.bar (bla) VALUES (1); SELECT pg_sleep(2); COMMIT;" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ADD COLUMN bar text;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ADD COLUMN bar text; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'cancel', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); subscriber_command -------------------- t (1 row) TABLE public.foo; id | bla | bar ----+-----+----- (0 rows) -- Now two processes to be killed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) -- This process will wait for the one above - but we want it to fail regardless of which gets killed first -- Avoid it firing our event triggers by using session_replication_role = replica \! PGOPTIONS='--client-min-messages=warning --session-replication-role=replica' psql -d contrib_regression -c "BEGIN; ALTER TABLE public.foo DROP COLUMN bar; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(2); pg_sleep ---------- (1 row) SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ADD COLUMN super text;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ADD COLUMN super text; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'terminate', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); subscriber_command -------------------- t (1 row) TABLE public.foo; id | bla | bar | super ----+-----+-----+------- (0 rows) /**** Try cancel_then_terminate, which should first try to cancel ****/ -- This process should be killed \! echo "BEGIN; SELECT * FROM public.foo;\n\! sleep 15" | psql contrib_regression > /dev/null 2>&1 & -- This process should not be killed \! psql contrib_regression -c "BEGIN; INSERT INTO public.bar (bla) VALUES (1); SELECT pg_sleep(5); COMMIT;" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ALTER COLUMN bar SET NOT NULL;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ALTER COLUMN bar SET NOT NULL; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'cancel_then_terminate', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); subscriber_command -------------------- t (1 row) TABLE public.foo; id | bla | bar | super ----+-----+-----+------- (0 rows) /*** TEST INHERITANCE AND PARTITIONING ***/ -- Same workflow as above, but instead select from child, alter parent \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo2; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); pg_sleep ---------- (1 row) SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('terminate','public','foo'); signal | successful | state | query | reported | pg_sleep -----------+------------+--------+--------------------------------------------------------+----------+---------- terminate | t | active | BEGIN; SELECT * FROM public.foo2; SELECT pg_sleep(30); | f | (1 row) /*** With <=1.5, it showed this. But it should kill the process. signal | successful | state | query | reported | pg_sleep --------+------------+-------+-------+----------+---------- (0 rows) ***/ DROP TABLE public.foo CASCADE; TABLE bar; id | bla ----+----- 1 | 1 2 | 1 (2 rows) DROP TABLE public.bar CASCADE; SELECT signal, successful, state, query, reported FROM pgl_ddl_deploy.killed_blockers ORDER BY signal, query; signal | successful | state | query | reported -----------+------------+---------------------+---------------------------------------------------------------------+---------- cancel | t | active | BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30); | f cancel | t | idle in transaction | SELECT * FROM public.foo; | f terminate | t | active | BEGIN; ALTER TABLE public.foo DROP COLUMN bar; SELECT pg_sleep(30); | f terminate | t | active | BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30); | f terminate | t | idle in transaction | SELECT * FROM public.foo; | f (5 rows) SELECT pg_sleep(1); pg_sleep ---------- (1 row) -- Should be zero - everything was killed SELECT COUNT(1) FROM pg_stat_activity WHERE usename = session_user AND NOT pid = pg_backend_pid() AND query LIKE '%public.foo%'; count ------- 0 (1 row) pgl_ddl_deploy-2.2.1/expected/54_new_setup.out000066400000000000000000000060371453171545300213660ustar00rootroot00000000000000--****NOTE*** this file drops the whole extension and all previous test setup. --If adding new tests, it is best to keep this file as the last test before cleanup. SET client_min_messages = warning; --Some day, we should regress with multiple databases. There are examples of this in pglogical code base --For now, we will mock the subscriber behavior, which is less than ideal, because it misses testing execution --on subscriber DROP EXTENSION pgl_ddl_deploy CASCADE; -- This test has been rewritten and presently exists for historical reasons and to maintain configuration CREATE EXTENSION pgl_ddl_deploy; SELECT set_driver(); set_driver ------------ (1 row) --These are the same sets as in the new_set_behavior.sql INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_2', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; --One include_schema_regex one that should be unchanged DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION testspecial; $sql$; ELSE CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('testspecial'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; END IF; END$$; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('testspecial','^special$',true, true); SELECT pgl_ddl_deploy.deploy('testspecial'); deploy -------- t (1 row) --These kinds of repsets will not replicate CREATE events, only ALTER TABLE, so deploy after CREATE --We assume schema will be copied to subscriber separately CREATE SCHEMA special; CREATE TABLE special.foo (id serial primary key, foo text, bar text); CREATE TABLE special.bar (id serial primary key, super text, man text); SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.foo'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_1'; add_table_to_replication -------------------------- t (1 row) SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.bar'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_2'; add_table_to_replication -------------------------- t (1 row) --Deploy by set_name SELECT pgl_ddl_deploy.deploy('my_special_tables_1'); deploy -------- t (1 row) SELECT pgl_ddl_deploy.deploy('my_special_tables_2'); deploy -------- t (1 row) pgl_ddl_deploy-2.2.1/expected/55_raise_message.out000066400000000000000000000020771453171545300221650ustar00rootroot00000000000000SET client_min_messages TO WARNING; ALTER EXTENSION pgl_ddl_deploy UPDATE; -- Simple example SELECT pgl_ddl_deploy.raise_message('WARNING', 'foo'); WARNING: foo raise_message --------------- t (1 row) -- Test case that needs % escapes SELECT pgl_ddl_deploy.raise_message('WARNING', $$SELECT foo FROM bar WHERE baz LIKE 'foo%'$$); WARNING: SELECT foo FROM bar WHERE baz LIKE 'foo%' raise_message --------------- t (1 row) /*** Failing message on 1.5 read: ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 3 SQL statement " DO $block$ BEGIN RAISE WARNING $pgl_ddl_deploy_msg$SELECT foo FROM bar WHERE baz LIKE 'foo%'$pgl_ddl_deploy_msg$; END$block$; " PL/pgSQL function pgl_ddl_deploy.raise_message(text,text) line 4 at EXECUTE ***/ SELECT * FROM pgl_ddl_deploy.exceptions; id | set_name | pid | executed_at | ddl_sql | err_msg | err_state | set_config_id | resolved | resolved_notes ----+----------+-----+-------------+---------+---------+-----------+---------------+----------+---------------- (0 rows) pgl_ddl_deploy-2.2.1/expected/56_1_6_features.out000066400000000000000000000040671453171545300216430ustar00rootroot00000000000000-- Configure this to only replicate functions or views -- This test is to ensure the config does NOT auto-add tables to replication (bug with <=1.5) UPDATE pgl_ddl_deploy.set_configs SET create_tags = '{"CREATE FUNCTION","ALTER FUNCTION","CREATE VIEW","ALTER VIEW"}' , drop_tags = '{"DROP FUNCTION","DROP VIEW"}' WHERE set_name = 'testspecial'; SELECT pgl_ddl_deploy.deploy('testspecial'); NOTICE: table "tmp_objs" does not exist, skipping deploy -------- t (1 row) CREATE TEMP VIEW tables_in_replication AS SELECT COUNT(1) FROM pgl_ddl_deploy.rep_set_table_wrapper() t WHERE t.name = 'testspecial' AND NOT relid::REGCLASS::TEXT = 'pgl_ddl_deploy.queue'; TABLE tables_in_replication; count ------- 2 (1 row) CREATE TABLE special.do_not_replicate_me(id int primary key); TABLE tables_in_replication; count ------- 2 (1 row) -- In <=1.5, this would have hit the code path to add new tables to replication, even though -- the set is configured not to replicate CREATE TABLE events CREATE FUNCTION special.do_replicate_me() RETURNS INT AS 'SELECT 1' LANGUAGE SQL; -- This SHOULD show the same as above, but showed 1 more table in <=1.5 TABLE tables_in_replication; count ------- 2 (1 row) -- Test to ensure we are only setting these defaults (trigger set_tag_defaults) on INSERT UPDATE pgl_ddl_deploy.set_configs SET drop_tags = NULL WHERE set_name = 'testspecial' RETURNING drop_tags; drop_tags ----------- (1 row) /* In <= 1.5, returned this: drop_tags -------------------------------------------------------------------------------------- {"DROP SCHEMA","DROP TABLE","DROP FUNCTION","DROP TYPE","DROP VIEW","DROP SEQUENCE"} (1 row) */ SET client_min_messages TO warning; DROP OWNED BY test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy; DROP ROLE unpriv; DROP EXTENSION pgl_ddl_deploy CASCADE; DROP EXTENSION IF EXISTS pglogical CASCADE; DROP SCHEMA IF EXISTS pglogical CASCADE; DROP TABLE IF EXISTS tmp_objs; DROP SCHEMA IF EXISTS special CASCADE; DROP SCHEMA IF EXISTS bla CASCADE; DROP SCHEMA IF EXISTS pgl_ddl_deploy CASCADE; pgl_ddl_deploy-2.2.1/expected/57_native_features.out000066400000000000000000000046111453171545300225400ustar00rootroot00000000000000SET client_min_messages = warning; DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN SET session_replication_role TO replica; ELSE CREATE EXTENSION pglogical; END IF; END$$; CREATE EXTENSION pgl_ddl_deploy; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN TRUE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; INSERT INTO pgl_ddl_deploy.queue (queued_at,role,pubnames,message_type,message) VALUES (now(),current_role,'{mock}'::TEXT[],pgl_ddl_deploy.queue_ddl_message_type(),'CREATE TABLE nativerox(id int)'); INSERT INTO pgl_ddl_deploy.queue (queued_at,role,pubnames,message_type,message) VALUES (now(),current_role,'{mock}'::TEXT[],pgl_ddl_deploy.queue_ddl_message_type(),'ALTER TABLE nativerox ADD COLUMN bar text;'); INSERT INTO pgl_ddl_deploy.queue (queued_at,role,pubnames,message_type,message) VALUES (now(),current_role,'{mock}'::TEXT[],pgl_ddl_deploy.queue_ddl_message_type(),$$SELECT pgl_ddl_deploy.notify_subscription_refresh('mock', true);$$); CREATE FUNCTION verify_count(ct int, expected int) RETURNS BOOLEAN AS $BODY$ BEGIN RAISE LOG 'ct: %', ct; IF ct != expected THEN RAISE EXCEPTION 'Count % does not match expected count of %', ct, expected; END IF; RETURN TRUE; END$BODY$ LANGUAGE plpgsql; DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN SELECT COUNT(1) INTO v_ct FROM information_schema.columns WHERE table_name = 'nativerox'; PERFORM verify_count(v_ct, 2); SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.subscriber_logs; PERFORM verify_count(v_ct, 1); PERFORM pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT (SELECT COUNT(1) FROM pgl_ddl_deploy.subscriber_logs WHERE NOT succeeded) + (SELECT COUNT(1) FROM pgl_ddl_deploy.subscriber_logs WHERE error_message ~* 'No subscription to publication mock exists') INTO v_ct; PERFORM verify_count(v_ct, 3); -- test for duplicate avoidance with multiple subscriptions SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.queue; PERFORM verify_count(v_ct, 3); SET session_replication_role TO replica; INSERT INTO pgl_ddl_deploy.queue SELECT * FROM pgl_ddl_deploy.queue; SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.queue; PERFORM verify_count(v_ct, 3); RESET session_replication_role; ELSE SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.subscriber_logs; PERFORM verify_count(v_ct, 0); END IF; END$$; pgl_ddl_deploy-2.2.1/functions/000077500000000000000000000000001453171545300165155ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/functions/add_ext_object.sql000066400000000000000000000003531453171545300221750ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object(p_type text, p_full_obj_name text) RETURNS void LANGUAGE plpgsql AS $function$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $function$ ;pgl_ddl_deploy-2.2.1/functions/add_role.sql000066400000000000000000000037761453171545300210240ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||';'; EXECUTE v_sql; IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; END IF; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/add_table_to_replication.sql000066400000000000000000000031401453171545300242260ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_table_to_replication(p_driver pgl_ddl_deploy.driver, p_set_name name, p_relation regclass, p_synchronize_data boolean DEFAULT false) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_schema NAME; v_table NAME; v_result BOOLEAN = false; BEGIN IF p_driver = 'pglogical' THEN SELECT pglogical.replication_set_add_table( set_name:=p_set_name ,relation:=p_relation ,synchronize_data:=p_synchronize_data ) INTO v_result; ELSEIF p_driver = 'native' THEN SELECT nspname, relname INTO v_schema, v_table FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.oid = p_relation::OID; EXECUTE 'ALTER PUBLICATION '||quote_ident(p_set_name)||' ADD TABLE '||quote_ident(v_schema)||'.'||quote_ident(v_table)||';'; -- We use true to synchronize data here, not taking the value from p_synchronize_data. This is because of the different way -- that native logical works, and that changes are not queued from the time of the table being added to replication. Thus, we -- by default WILL use COPY_DATA = true -- This needs to be in a DO block currently because of how the DDL is processed on the subscriber. PERFORM pgl_ddl_deploy.replicate_ddl_command($$DO $AUTO_REPLICATE_BLOCK$ BEGIN PERFORM pgl_ddl_deploy.notify_subscription_refresh('$$||p_set_name||$$', true); END$AUTO_REPLICATE_BLOCK$;$$, array[p_set_name]); v_result = true; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; RETURN v_result; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/blacklisted_tags.sql000066400000000000000000000004721453171545300225400ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $function$ ; pgl_ddl_deploy-2.2.1/functions/common_exclude_alter_table_subcommands.sql000066400000000000000000000015611453171545300271730ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.common_exclude_alter_table_subcommands() RETURNS TEXT[] AS $BODY$ SELECT ARRAY[ 'ADD CONSTRAINT', 'ADD CONSTRAINT (and recurse)', '(re) ADD CONSTRAINT', 'ALTER CONSTRAINT', 'VALIDATE CONSTRAINT', 'VALIDATE CONSTRAINT (and recurse)', 'ADD (processed) CONSTRAINT', 'ADD CONSTRAINT (using index)', 'DROP CONSTRAINT', 'DROP CONSTRAINT (and recurse)', 'SET LOGGED', 'SET UNLOGGED', 'SET TABLESPACE', 'SET RELOPTIONS', 'RESET RELOPTIONS', 'REPLACE RELOPTIONS', 'ENABLE TRIGGER', 'ENABLE TRIGGER (always)', 'ENABLE TRIGGER (replica)', 'DISABLE TRIGGER', 'ENABLE TRIGGER (all)', 'DISABLE TRIGGER (all)', 'ENABLE TRIGGER (user)', 'DISABLE TRIGGER (user)', 'ENABLE RULE', 'ENABLE RULE (always)', 'ENABLE RULE (replica)', 'DISABLE RULE', 'SET OPTIONS']::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE;pgl_ddl_deploy-2.2.1/functions/deploy.sql000066400000000000000000000015421453171545300205340ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; pgl_ddl_deploy-2.2.1/functions/deployment_check.sql000066400000000000000000000007001453171545300225500ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id, NULL); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(NULL, p_set_name); END; $function$; pgl_ddl_deploy-2.2.1/functions/deployment_check_count.sql000066400000000000000000000037671453171545300240000ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text, p_driver pgl_ddl_deploy.driver) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = p_set_name AND rsr.relid = c.oid AND rsr.driver = p_driver); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = '$SQL$||p_set_name||$SQL$' AND rsr.relid = c.oid AND rsr.driver = (SELECT driver FROM pgl_ddl_deploy.set_configs WHERE set_name = '$SQL$||p_set_name||$SQL$')); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/deployment_check_wrapper.sql000066400000000000000000000033531453171545300243170ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; v_driver pgl_ddl_deploy.driver; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication, driver INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication, v_driver FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex, v_driver); END; $function$; pgl_ddl_deploy-2.2.1/functions/disable.sql000066400000000000000000000004131453171545300206370ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/drop_ext_object.sql000066400000000000000000000003551453171545300224130ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object(p_type text, p_full_obj_name text) RETURNS void LANGUAGE plpgsql AS $function$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $function$ ;pgl_ddl_deploy-2.2.1/functions/enable.sql000066400000000000000000000006561453171545300204730ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/exclude_regex.sql000066400000000000000000000003631453171545300220630ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$'::TEXT; $function$ ;pgl_ddl_deploy-2.2.1/functions/execute_queued_ddl.sql000066400000000000000000000036441453171545300231020ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ -- This handles potential duplicates with multiple subscriptions to same publisher db. IF EXISTS ( SELECT NEW.* INTERSECT SELECT * FROM pgl_ddl_deploy.queue) THEN RETURN NULL; END IF; IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/fail_queued_attempt.sql000066400000000000000000000016651453171545300232670ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id integer, p_error_message text) RETURNS void LANGUAGE plpgsql AS $function$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/get_altertable_subcmdinfo.sql000066400000000000000000000002721453171545300244260ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; pgl_ddl_deploy-2.2.1/functions/get_command_tag.sql000066400000000000000000000002301453171545300223410ustar00rootroot00000000000000CREATE FUNCTION pgl_ddl_deploy.get_command_tag(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_tag' LANGUAGE C;pgl_ddl_deploy-2.2.1/functions/get_command_type.sql000066400000000000000000000002321453171545300225510ustar00rootroot00000000000000CREATE FUNCTION pgl_ddl_deploy.get_command_type(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_type' LANGUAGE C;pgl_ddl_deploy-2.2.1/functions/is_subscriber.sql000066400000000000000000000013431453171545300220750ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.is_subscriber(p_driver pgl_ddl_deploy.driver, p_name TEXT[], p_provider_name NAME = NULL) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN IF p_driver = 'pglogical' THEN RETURN EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_name); ELSEIF p_driver = 'native' THEN RETURN EXISTS (SELECT 1 FROM pg_subscription s WHERE subpublications && p_name); ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/kill_blockers.sql000066400000000000000000000062271453171545300220640ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT DISTINCT ON (l.pid) p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid /*** We need to check if this is an inheritance parent, because even a share lock on a child will prevent DDL on parent ***/ LEFT JOIN pg_inherits pi ON pi.inhrelid = c.oid LEFT JOIN pg_class ipc on ipc.oid = pi.inhparent LEFT JOIN pg_namespace ipn on ipn.oid = ipc.relnamespace -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND ((n.nspname = p_nspname AND c.relname = p_relname) OR (ipn.nspname = p_nspname AND ipc.relname = p_relname)) AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY l.pid, a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; pgl_ddl_deploy-2.2.1/functions/lock_safe_executor.sql000066400000000000000000000006411453171545300231030ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql text) RETURNS void LANGUAGE plpgsql AS $function$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/log_unhandled.sql000066400000000000000000000012251453171545300220410ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.log_unhandled(p_set_config_id integer, p_set_name text, p_pid integer, p_ddl_sql_raw text, p_command_tag text, p_reason text, p_txid bigint) RETURNS void LANGUAGE plpgsql AS $function$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/notify_subscription_refresh.sql000066400000000000000000000015341453171545300250730ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.notify_subscription_refresh(p_set_name name, p_copy_data boolean DEFAULT TRUE) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pg_subscription WHERE subpublications && array[p_set_name::text]) THEN RAISE EXCEPTION 'No subscription to publication % exists', p_set_name; END IF; FOR v_rec IN SELECT unnest(subpublications) AS pubname, subname FROM pg_subscription WHERE subpublications && array[p_set_name::text] LOOP v_sql = $$ALTER SUBSCRIPTION $$||quote_ident(v_rec.subname)||$$ REFRESH PUBLICATION WITH ( COPY_DATA = '$$||p_copy_data||$$');$$; RAISE LOG 'pgl_ddl_deploy executing: %', v_sql; EXECUTE v_sql; END LOOP; RETURN TRUE; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/override.sql000066400000000000000000000002131453171545300210510ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; pgl_ddl_deploy-2.2.1/functions/provider_node_name.sql000066400000000000000000000007601453171545300231000ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.provider_node_name(p_driver pgl_ddl_deploy.driver) RETURNS NAME LANGUAGE plpgsql AS $function$ DECLARE v_node_name NAME; BEGIN IF p_driver = 'pglogical' THEN SELECT n.node_name INTO v_node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id); RETURN v_node_name; ELSEIF p_driver = 'native' THEN RETURN NULL::NAME; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/queue_ddl_message_type.sql000066400000000000000000000002341453171545300237510ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.queue_ddl_message_type() RETURNS "char" LANGUAGE sql IMMUTABLE AS $function$ SELECT 'Q'::"char"; $function$ ; pgl_ddl_deploy-2.2.1/functions/raise_message.sql000066400000000000000000000005051453171545300220450ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, REPLACE(p_message,'%','%%')); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; pgl_ddl_deploy-2.2.1/functions/rep_set_table_wrapper.sql000066400000000000000000000053561453171545300236170ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (id OID, relid REGCLASS, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id); ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id); ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; END IF; END IF; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/rep_set_wrapper.sql000066400000000000000000000025671453171545300224510ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_wrapper() RETURNS TABLE (id OID, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs; ELSE RAISE EXCEPTION 'pglogical required for version prior to Postgres 10'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs UNION ALL SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSE RAISE EXCEPTION 'Unexpected exception'; END IF; END IF; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/replicate_ddl_command.sql000066400000000000000000000010551453171545300235300ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.replicate_ddl_command(command text, pubnames text[]) RETURNS BOOLEAN LANGUAGE plpgsql AS $function$ -- Modeled after pglogical's replicate_ddl_command but in support of native logical replication BEGIN -- NOTE: pglogical uses clock_timestamp() to log queued_at times and we do the same here INSERT INTO pgl_ddl_deploy.queue (queued_at, role, pubnames, message_type, message) VALUES (clock_timestamp(), current_role, pubnames, pgl_ddl_deploy.queue_ddl_message_type(), command); RETURN TRUE; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/resolve_exception.sql000066400000000000000000000006241453171545300227750ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id integer, p_notes text DEFAULT NULL::text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $function$ ;pgl_ddl_deploy-2.2.1/functions/resolve_unhandled.sql000066400000000000000000000006231453171545300227400ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id integer, p_notes text DEFAULT NULL::text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $function$ ;pgl_ddl_deploy-2.2.1/functions/retry_all_subscriber_logs.sql000066400000000000000000000013421453171545300245020ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS boolean[] LANGUAGE plpgsql AS $function$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/retry_subscriber_log.sql000066400000000000000000000041741453171545300234750ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/schema_execute.sql000066400000000000000000000012431453171545300222200ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id integer, p_field_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/set_origin_subscriber_log_id.sql000066400000000000000000000003041453171545300251350ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/set_tag_defaults.sql000066400000000000000000000007631453171545300225610ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN pgl_ddl_deploy.standard_repset_only_tags() ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $function$ ;pgl_ddl_deploy-2.2.1/functions/sql_command_tags.sql000066400000000000000000000002561453171545300225540ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql text) RETURNS text[] LANGUAGE c STRICT AS '$libdir/pgl_ddl_deploy', $function$sql_command_tags$function$ ;pgl_ddl_deploy-2.2.1/functions/standard_create_tags.sql000066400000000000000000000006461453171545300234050ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT ,"CREATE RULE" ,"CREATE TRIGGER" ,"ALTER TRIGGER"}'::TEXT[]; $function$ ; pgl_ddl_deploy-2.2.1/functions/standard_drop_tags.sql000066400000000000000000000004001453171545300230720ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $function$ ;pgl_ddl_deploy-2.2.1/functions/standard_repset_only_tags.sql000066400000000000000000000002711453171545300244770ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_repset_only_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,COMMENT}'::TEXT[]; $function$ ;pgl_ddl_deploy-2.2.1/functions/subscriber_command.sql000066400000000000000000000123531453171545300231030ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, p_driver pgl_ddl_deploy.driver, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $function$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN IF pgl_ddl_deploy.is_subscriber(p_driver, p_set_name, p_provider_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $function$ LANGUAGE plpgsql VOLATILE; pgl_ddl_deploy-2.2.1/functions/toggle_ext_object.sql000066400000000000000000000015071453171545300227300ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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$ ;pgl_ddl_deploy-2.2.1/functions/undeploy.sql000066400000000000000000000006561453171545300211040ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $function$ ; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; pgl_ddl_deploy-2.2.1/functions/unique_tags.sql000066400000000000000000000026161453171545300215670ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_output TEXT; BEGIN WITH dupes AS ( SELECT set_name, CASE WHEN include_only_repset_tables THEN 'include_only_repset_tables' WHEN include_everything AND NOT ddl_only_replication THEN 'include_everything' WHEN include_schema_regex IS NOT NULL AND NOT ddl_only_replication THEN 'include_schema_regex' WHEN ddl_only_replication THEN CASE WHEN include_everything THEN 'ddl_only_include_everything' WHEN include_schema_regex IS NOT NULL THEN 'ddl_only_include_schema_regex' END END AS category, unnest(array_cat(create_tags, drop_tags)) AS command_tag FROM pgl_ddl_deploy.set_configs GROUP BY 1, 2, 3 HAVING COUNT(1) > 1) , aggregate_dupe_tags AS ( SELECT set_name, category, string_agg(command_tag, ', ' ORDER BY command_tag) AS command_tags FROM dupes GROUP BY 1, 2 ) SELECT string_agg(format('%s: %s: %s', set_name, category, command_tags), ', ') AS output INTO v_output FROM aggregate_dupe_tags; IF v_output IS NOT NULL THEN RAISE EXCEPTION '%', format('You have overlapping configuration types and command tags which is not permitted: %s', v_output); END IF; RETURN NULL; END; $function$ ; pgl_ddl_deploy-2.2.1/functions/unsupported_tags.sql000066400000000000000000000002751453171545300226500ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $function$ ;pgl_ddl_deploy-2.2.1/generate_new_native_tests.py000077500000000000000000000060741453171545300223240ustar00rootroot00000000000000#!/usr/bin/env python3 from shutil import copyfile import glob import os sql = './sql' expected = './expected' NEW_FILES = ['native_features'] for file in NEW_FILES: filelist = glob.glob(f"{sql}/*{file}.sql") for path in filelist: try: os.remove(path) except: print("Error while deleting file : ", path) filelist = glob.glob(f"{expected}/*{file}.out") for path in filelist: try: os.remove(path) except: print("Error while deleting file : ", path) files = {} for filename in os.listdir(sql): split_filename = filename.split("_", 1) number = int(split_filename[0]) files[number] = split_filename[1] max_file_num = max(files.keys()) def construct_filename(n, name): return f"{str(n).zfill(2)}_{name}" contents = """ SET client_min_messages = warning; DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN SET session_replication_role TO replica; ELSE CREATE EXTENSION pglogical; END IF; END$$; CREATE EXTENSION pgl_ddl_deploy; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN TRUE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; INSERT INTO pgl_ddl_deploy.queue (queued_at,role,pubnames,message_type,message) VALUES (now(),current_role,'{mock}'::TEXT[],pgl_ddl_deploy.queue_ddl_message_type(),'CREATE TABLE nativerox(id int)'); INSERT INTO pgl_ddl_deploy.queue (queued_at,role,pubnames,message_type,message) VALUES (now(),current_role,'{mock}'::TEXT[],pgl_ddl_deploy.queue_ddl_message_type(),'ALTER TABLE nativerox ADD COLUMN bar text;'); INSERT INTO pgl_ddl_deploy.queue (queued_at,role,pubnames,message_type,message) VALUES (now(),current_role,'{mock}'::TEXT[],pgl_ddl_deploy.queue_ddl_message_type(),$$SELECT pgl_ddl_deploy.notify_subscription_refresh('mock', true);$$); DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN SELECT COUNT(1) INTO v_ct FROM information_schema.columns WHERE table_name = 'nativerox'; RAISE LOG 'v_ct: %', v_ct; IF v_ct != 2 THEN RAISE EXCEPTION 'Count does not match expected: v_ct: %', v_ct; END IF; SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.subscriber_logs; IF v_ct != 1 THEN RAISE EXCEPTION 'Count does not match expected: v_ct: %', v_ct; END IF; PERFORM pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT (SELECT COUNT(1) FROM pgl_ddl_deploy.subscriber_logs WHERE NOT succeeded) + (SELECT COUNT(1) FROM pgl_ddl_deploy.subscriber_logs WHERE error_message ~* 'No subscription to publication mock exists') INTO v_ct; IF v_ct != 3 THEN RAISE EXCEPTION 'Count does not match expected: v_ct: %', v_ct; END IF; ELSE SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.subscriber_logs; IF v_ct != 0 THEN RAISE EXCEPTION 'Count does not match expected: v_ct: %', v_ct; END IF; END IF; END$$; """ fname = construct_filename(max_file_num + 1, 'native_features') with open(f"{sql}/{fname}.sql", "w") as newfile: newfile.write(contents) copyfile(f"{sql}/{fname}.sql", f"{expected}/{fname}.out") pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.0--1.1.sql000066400000000000000000001262241453171545300211410ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /*** 2 Changes: - This was causing issues due to event triggers firing. Disable via session_replication_role. - We need to re-grant access to the view after dependency_update. ****/ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql SET SESSION_REPLICATION_ROLE TO REPLICA; /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id int, p_set_name text, p_include_schema_regex text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.0.sql000066400000000000000000000746541453171545300206000ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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; /*** pglogical version-specific handling This is not sufficient if pglogical is upgraded underneath an installation of pgl_ddl_deploy, but at least will support either version at install. If you indeed were to do that, you will likely start to see WARNING level logs indicating a problem. DDL statements should not fail. To correct the problem manually, run pgl_ddl_deploy.dependency_update() ****/ CREATE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql; SELECT pgl_ddl_deploy.dependency_update(); CREATE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = '$BUILD$||include_schema_regex||$BUILD$'; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT pgl_ddl_deploy.blacklisted_tags() && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; --Log change on subscriber INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, executed_at, ddl_sql) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex) THEN 1 ELSE 0 END) AS relevant_schema_count INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands(); $BUILD$::TEXT AS shared_objects_check FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT set_name, auto_replication_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) THEN 1 ELSE 0 END) AS relevant_schema_count , SUM(CASE WHEN (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) THEN 1 ELSE 0 END) AS excluded_schema_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'ALTER TABLE' ,'CREATE SEQUENCE' ,'ALTER SEQUENCE' ,'CREATE SCHEMA' ,'CREATE TABLE' ,'CREATE FUNCTION' ,'ALTER FUNCTION' ,'CREATE TYPE' ,'ALTER TYPE' ,'CREATE VIEW' ,'ALTER VIEW') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN( 'DROP SCHEMA' ,'DROP TABLE' ,'DROP FUNCTION' ,'DROP TYPE' ,'DROP VIEW' ,'DROP SEQUENCE') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'CREATE TABLE AS' ,'SELECT INTO' ) --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.set_name, b.auto_replication_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_include_schema_regex TEXT; BEGIN SELECT include_schema_regex INTO c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for set % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||c_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.1--1.2.sql000066400000000000000000000543111453171545300211400ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit --These unsupported event triggers could have been erroneously added in v. 1.1 for include_only_repset_table configs SELECT pgl_ddl_deploy.drop_ext_object('EVENT TRIGGER',auto_replication_unsupported_trigger_name), pgl_ddl_deploy.drop_ext_object('FUNCTION',auto_replication_unsupported_function_name||'()') FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables; DO $$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT ets.auto_replication_unsupported_trigger_name, ets.auto_replication_unsupported_function_name FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables LOOP v_sql:='DROP EVENT TRIGGER IF EXISTS '||v_rec.auto_replication_unsupported_trigger_name||'; DROP FUNCTION IF EXISTS '||v_rec.auto_replication_unsupported_function_name||'();'; EXECUTE v_sql; END LOOP; END$$; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; --Need this for unprivileged users to be able to run the function and check if tables are repset tables GRANT SELECT ON TABLE pglogical.replication_set TO PUBLIC; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.1.sql000066400000000000000000002231001453171545300205570ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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; /*** pglogical version-specific handling This is not sufficient if pglogical is upgraded underneath an installation of pgl_ddl_deploy, but at least will support either version at install. If you indeed were to do that, you will likely start to see WARNING level logs indicating a problem. DDL statements should not fail. To correct the problem manually, run pgl_ddl_deploy.dependency_update() ****/ CREATE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql; SELECT pgl_ddl_deploy.dependency_update(); CREATE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = '$BUILD$||include_schema_regex||$BUILD$'; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT pgl_ddl_deploy.blacklisted_tags() && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; --Log change on subscriber INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, executed_at, ddl_sql) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex) THEN 1 ELSE 0 END) AS relevant_schema_count INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands(); $BUILD$::TEXT AS shared_objects_check FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT set_name, auto_replication_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) THEN 1 ELSE 0 END) AS relevant_schema_count , SUM(CASE WHEN (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) THEN 1 ELSE 0 END) AS excluded_schema_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'ALTER TABLE' ,'CREATE SEQUENCE' ,'ALTER SEQUENCE' ,'CREATE SCHEMA' ,'CREATE TABLE' ,'CREATE FUNCTION' ,'ALTER FUNCTION' ,'CREATE TYPE' ,'ALTER TYPE' ,'CREATE VIEW' ,'ALTER VIEW') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN( 'DROP SCHEMA' ,'DROP TABLE' ,'DROP FUNCTION' ,'DROP TYPE' ,'DROP VIEW' ,'DROP SEQUENCE') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'CREATE TABLE AS' ,'SELECT INTO' ) --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.set_name, b.auto_replication_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_include_schema_regex TEXT; BEGIN SELECT include_schema_regex INTO c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for set % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||c_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /*** 2 Changes: - This was causing issues due to event triggers firing. Disable via session_replication_role. - We need to re-grant access to the view after dependency_update. ****/ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql SET SESSION_REPLICATION_ROLE TO REPLICA; /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id int, p_set_name text, p_include_schema_regex text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.2--1.3.sql000066400000000000000000000542741453171545300211520ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; /**** Drop any deployed event triggers for include_only_repset_tables and recreate now with fixed function def. ****/ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed AND include_only_repset_tables; SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.2.sql000066400000000000000000002774111453171545300205760ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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; /*** pglogical version-specific handling This is not sufficient if pglogical is upgraded underneath an installation of pgl_ddl_deploy, but at least will support either version at install. If you indeed were to do that, you will likely start to see WARNING level logs indicating a problem. DDL statements should not fail. To correct the problem manually, run pgl_ddl_deploy.dependency_update() ****/ CREATE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql; SELECT pgl_ddl_deploy.dependency_update(); CREATE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = '$BUILD$||include_schema_regex||$BUILD$'; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT pgl_ddl_deploy.blacklisted_tags() && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; --Log change on subscriber INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, executed_at, ddl_sql) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex) THEN 1 ELSE 0 END) AS relevant_schema_count INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands(); $BUILD$::TEXT AS shared_objects_check FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT set_name, auto_replication_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) THEN 1 ELSE 0 END) AS relevant_schema_count , SUM(CASE WHEN (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) THEN 1 ELSE 0 END) AS excluded_schema_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'ALTER TABLE' ,'CREATE SEQUENCE' ,'ALTER SEQUENCE' ,'CREATE SCHEMA' ,'CREATE TABLE' ,'CREATE FUNCTION' ,'ALTER FUNCTION' ,'CREATE TYPE' ,'ALTER TYPE' ,'CREATE VIEW' ,'ALTER VIEW') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN( 'DROP SCHEMA' ,'DROP TABLE' ,'DROP FUNCTION' ,'DROP TYPE' ,'DROP VIEW' ,'DROP SEQUENCE') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'CREATE TABLE AS' ,'SELECT INTO' ) --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.set_name, b.auto_replication_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_include_schema_regex TEXT; BEGIN SELECT include_schema_regex INTO c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for set % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||c_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /*** 2 Changes: - This was causing issues due to event triggers firing. Disable via session_replication_role. - We need to re-grant access to the view after dependency_update. ****/ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql SET SESSION_REPLICATION_ROLE TO REPLICA; /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id int, p_set_name text, p_include_schema_regex text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit --These unsupported event triggers could have been erroneously added in v. 1.1 for include_only_repset_table configs SELECT pgl_ddl_deploy.drop_ext_object('EVENT TRIGGER',auto_replication_unsupported_trigger_name), pgl_ddl_deploy.drop_ext_object('FUNCTION',auto_replication_unsupported_function_name||'()') FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables; DO $$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT ets.auto_replication_unsupported_trigger_name, ets.auto_replication_unsupported_function_name FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables LOOP v_sql:='DROP EVENT TRIGGER IF EXISTS '||v_rec.auto_replication_unsupported_trigger_name||'; DROP FUNCTION IF EXISTS '||v_rec.auto_replication_unsupported_function_name||'();'; EXECUTE v_sql; END LOOP; END$$; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; --Need this for unprivileged users to be able to run the function and check if tables are repset tables GRANT SELECT ON TABLE pglogical.replication_set TO PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.3--1.4.sql000066400000000000000000001025551453171545300211500ustar00rootroot00000000000000/* pgl_ddl_deploy--1.3--1.4.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; SELECT pgl_ddl_deploy.drop_ext_object('FUNCTION','pgl_ddl_deploy.dependency_update()'); DROP FUNCTION pgl_ddl_deploy.dependency_update(); SELECT pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN exclude_alter_table_subcommands TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_only_alter_table; SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs_id_seq', ''); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN ddl_only_replication BOOLEAN NOT NULL DEFAULT FALSE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table 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 pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id, NULL); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(NULL, p_set_name); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_tag(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_tag' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_type(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_type' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_repset_only_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$'::TEXT; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.common_exclude_alter_table_subcommands() RETURNS TEXT[] AS $BODY$ SELECT ARRAY[ 'ADD CONSTRAINT', 'ADD CONSTRAINT (and recurse)', '(re) ADD CONSTRAINT', 'ALTER CONSTRAINT', 'VALIDATE CONSTRAINT', 'VALIDATE CONSTRAINT (and recurse)', 'ADD (processed) CONSTRAINT', 'ADD CONSTRAINT (using index)', 'DROP CONSTRAINT', 'DROP CONSTRAINT (and recurse)', 'SET LOGGED', 'SET UNLOGGED', 'SET TABLESPACE', 'SET RELOPTIONS', 'RESET RELOPTIONS', 'REPLACE RELOPTIONS', 'ENABLE TRIGGER', 'ENABLE TRIGGER (always)', 'ENABLE TRIGGER (replica)', 'DISABLE TRIGGER', 'ENABLE TRIGGER (all)', 'DISABLE TRIGGER (all)', 'ENABLE TRIGGER (user)', 'DISABLE TRIGGER (user)', 'ENABLE RULE', 'ENABLE RULE (always)', 'ENABLE RULE (replica)', 'DISABLE RULE', 'SET OPTIONS']::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF NOT NEW.ddl_only_replication AND EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND NOT NEW.ddl_only_replication AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs, unless you are using the ddl_only_replication setting. $$, NEW.set_name; END IF; RETURN NEW; END; $function$ ; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_restricted_tags CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND pgl_ddl_deploy.standard_repset_only_tags() @> create_tags AND drop_tags IS NULL)); SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.3.sql000066400000000000000000003537051453171545300206000ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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; /*** pglogical version-specific handling This is not sufficient if pglogical is upgraded underneath an installation of pgl_ddl_deploy, but at least will support either version at install. If you indeed were to do that, you will likely start to see WARNING level logs indicating a problem. DDL statements should not fail. To correct the problem manually, run pgl_ddl_deploy.dependency_update() ****/ CREATE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql; SELECT pgl_ddl_deploy.dependency_update(); CREATE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = '$BUILD$||include_schema_regex||$BUILD$'; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT pgl_ddl_deploy.blacklisted_tags() && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; --Log change on subscriber INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, executed_at, ddl_sql) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex) THEN 1 ELSE 0 END) AS relevant_schema_count INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands(); $BUILD$::TEXT AS shared_objects_check FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT set_name, auto_replication_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) THEN 1 ELSE 0 END) AS relevant_schema_count , SUM(CASE WHEN (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) THEN 1 ELSE 0 END) AS excluded_schema_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'ALTER TABLE' ,'CREATE SEQUENCE' ,'ALTER SEQUENCE' ,'CREATE SCHEMA' ,'CREATE TABLE' ,'CREATE FUNCTION' ,'ALTER FUNCTION' ,'CREATE TYPE' ,'ALTER TYPE' ,'CREATE VIEW' ,'ALTER VIEW') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN( 'DROP SCHEMA' ,'DROP TABLE' ,'DROP FUNCTION' ,'DROP TYPE' ,'DROP VIEW' ,'DROP SEQUENCE') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'CREATE TABLE AS' ,'SELECT INTO' ) --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.set_name, b.auto_replication_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_include_schema_regex TEXT; BEGIN SELECT include_schema_regex INTO c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for set % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||c_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /*** 2 Changes: - This was causing issues due to event triggers firing. Disable via session_replication_role. - We need to re-grant access to the view after dependency_update. ****/ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql SET SESSION_REPLICATION_ROLE TO REPLICA; /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id int, p_set_name text, p_include_schema_regex text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit --These unsupported event triggers could have been erroneously added in v. 1.1 for include_only_repset_table configs SELECT pgl_ddl_deploy.drop_ext_object('EVENT TRIGGER',auto_replication_unsupported_trigger_name), pgl_ddl_deploy.drop_ext_object('FUNCTION',auto_replication_unsupported_function_name||'()') FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables; DO $$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT ets.auto_replication_unsupported_trigger_name, ets.auto_replication_unsupported_function_name FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables LOOP v_sql:='DROP EVENT TRIGGER IF EXISTS '||v_rec.auto_replication_unsupported_trigger_name||'; DROP FUNCTION IF EXISTS '||v_rec.auto_replication_unsupported_function_name||'();'; EXECUTE v_sql; END LOOP; END$$; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; --Need this for unprivileged users to be able to run the function and check if tables are repset tables GRANT SELECT ON TABLE pglogical.replication_set TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; /**** Drop any deployed event triggers for include_only_repset_tables and recreate now with fixed function def. ****/ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed AND include_only_repset_tables; SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.4--1.5.sql000066400000000000000000001174771453171545300211630ustar00rootroot00000000000000/* pgl_ddl_deploy--1.4--1.5.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_everything BOOLEAN NOT NULL DEFAULT FALSE; -- Now we have 3 configuration types ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_or_regex_inclusion; -- Only allow one of them to be chosen ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT single_configuration_type CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL) OR (include_everything AND NOT include_only_repset_tables AND include_schema_regex IS NULL)); ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT ddl_only_restrictions CHECK (NOT (ddl_only_replication AND include_only_repset_tables)); -- Need to adjust to after trigger and change function def DROP TRIGGER unique_tags ON pgl_ddl_deploy.set_configs; DROP FUNCTION pgl_ddl_deploy.unique_tags(); -- We need to add the column include_everything to it in a nice order DROP VIEW pgl_ddl_deploy.event_trigger_schema; -- Support canceling or terminating blocking processes on subscriber CREATE TYPE pgl_ddl_deploy.signals AS ENUM ('cancel','terminate','cancel_then_terminate'); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN signal_blocking_subscriber_sessions pgl_ddl_deploy.signals; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN subscriber_lock_timeout INT; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_signal_blocker_config CHECK (NOT (lock_safe_deployment AND (signal_blocking_subscriber_sessions IS NOT NULL OR subscriber_lock_timeout IS NOT NULL)) AND NOT (subscriber_lock_timeout IS NOT NULL AND signal_blocking_subscriber_sessions IS NULL)); CREATE TABLE pgl_ddl_deploy.killed_blockers ( id SERIAL PRIMARY KEY, signal TEXT, successful BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN DEFAULT FALSE, reported_at TIMESTAMPTZ ); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_output TEXT; BEGIN WITH dupes AS ( SELECT set_name, CASE WHEN include_only_repset_tables THEN 'include_only_repset_tables' WHEN include_everything AND NOT ddl_only_replication THEN 'include_everything' WHEN include_schema_regex IS NOT NULL AND NOT ddl_only_replication THEN 'include_schema_regex' WHEN ddl_only_replication THEN CASE WHEN include_everything THEN 'ddl_only_include_everything' WHEN include_schema_regex IS NOT NULL THEN 'ddl_only_include_schema_regex' END END AS category, unnest(array_cat(create_tags, drop_tags)) AS command_tag FROM pgl_ddl_deploy.set_configs GROUP BY 1, 2, 3 HAVING COUNT(1) > 1) , aggregate_dupe_tags AS ( SELECT set_name, category, string_agg(command_tag, ', ' ORDER BY command_tag) AS command_tags FROM dupes GROUP BY 1, 2 ) SELECT string_agg(format('%s: %s: %s', set_name, category, command_tags), ', ') AS output INTO v_output FROM aggregate_dupe_tags; IF v_output IS NOT NULL THEN RAISE EXCEPTION '%', format('You have overlapping configuration types and command tags which is not permitted: %s', v_output); END IF; RETURN NULL; END; $function$ ; CREATE TRIGGER unique_tags AFTER INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND n.nspname = p_nspname AND c.relname = p_relname AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(name, regclass, boolean, text[], text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_set_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, p_message); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||quote_literal(c_provider_name)||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG '%', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.4.sql000066400000000000000000004564621453171545300206050ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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; /*** pglogical version-specific handling This is not sufficient if pglogical is upgraded underneath an installation of pgl_ddl_deploy, but at least will support either version at install. If you indeed were to do that, you will likely start to see WARNING level logs indicating a problem. DDL statements should not fail. To correct the problem manually, run pgl_ddl_deploy.dependency_update() ****/ CREATE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql; SELECT pgl_ddl_deploy.dependency_update(); CREATE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = '$BUILD$||include_schema_regex||$BUILD$'; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT pgl_ddl_deploy.blacklisted_tags() && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; --Log change on subscriber INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, executed_at, ddl_sql) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex) THEN 1 ELSE 0 END) AS relevant_schema_count INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands(); $BUILD$::TEXT AS shared_objects_check FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT set_name, auto_replication_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) THEN 1 ELSE 0 END) AS relevant_schema_count , SUM(CASE WHEN (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) THEN 1 ELSE 0 END) AS excluded_schema_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'ALTER TABLE' ,'CREATE SEQUENCE' ,'ALTER SEQUENCE' ,'CREATE SCHEMA' ,'CREATE TABLE' ,'CREATE FUNCTION' ,'ALTER FUNCTION' ,'CREATE TYPE' ,'ALTER TYPE' ,'CREATE VIEW' ,'ALTER VIEW') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN( 'DROP SCHEMA' ,'DROP TABLE' ,'DROP FUNCTION' ,'DROP TYPE' ,'DROP VIEW' ,'DROP SEQUENCE') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'CREATE TABLE AS' ,'SELECT INTO' ) --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.set_name, b.auto_replication_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_include_schema_regex TEXT; BEGIN SELECT include_schema_regex INTO c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for set % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||c_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /*** 2 Changes: - This was causing issues due to event triggers firing. Disable via session_replication_role. - We need to re-grant access to the view after dependency_update. ****/ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql SET SESSION_REPLICATION_ROLE TO REPLICA; /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id int, p_set_name text, p_include_schema_regex text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit --These unsupported event triggers could have been erroneously added in v. 1.1 for include_only_repset_table configs SELECT pgl_ddl_deploy.drop_ext_object('EVENT TRIGGER',auto_replication_unsupported_trigger_name), pgl_ddl_deploy.drop_ext_object('FUNCTION',auto_replication_unsupported_function_name||'()') FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables; DO $$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT ets.auto_replication_unsupported_trigger_name, ets.auto_replication_unsupported_function_name FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables LOOP v_sql:='DROP EVENT TRIGGER IF EXISTS '||v_rec.auto_replication_unsupported_trigger_name||'; DROP FUNCTION IF EXISTS '||v_rec.auto_replication_unsupported_function_name||'();'; EXECUTE v_sql; END LOOP; END$$; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; --Need this for unprivileged users to be able to run the function and check if tables are repset tables GRANT SELECT ON TABLE pglogical.replication_set TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; /**** Drop any deployed event triggers for include_only_repset_tables and recreate now with fixed function def. ****/ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed AND include_only_repset_tables; SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; /* pgl_ddl_deploy--1.3--1.4.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; SELECT pgl_ddl_deploy.drop_ext_object('FUNCTION','pgl_ddl_deploy.dependency_update()'); DROP FUNCTION pgl_ddl_deploy.dependency_update(); SELECT pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN exclude_alter_table_subcommands TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_only_alter_table; SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs_id_seq', ''); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN ddl_only_replication BOOLEAN NOT NULL DEFAULT FALSE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table 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 pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id, NULL); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(NULL, p_set_name); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_tag(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_tag' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_type(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_type' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_repset_only_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$'::TEXT; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.common_exclude_alter_table_subcommands() RETURNS TEXT[] AS $BODY$ SELECT ARRAY[ 'ADD CONSTRAINT', 'ADD CONSTRAINT (and recurse)', '(re) ADD CONSTRAINT', 'ALTER CONSTRAINT', 'VALIDATE CONSTRAINT', 'VALIDATE CONSTRAINT (and recurse)', 'ADD (processed) CONSTRAINT', 'ADD CONSTRAINT (using index)', 'DROP CONSTRAINT', 'DROP CONSTRAINT (and recurse)', 'SET LOGGED', 'SET UNLOGGED', 'SET TABLESPACE', 'SET RELOPTIONS', 'RESET RELOPTIONS', 'REPLACE RELOPTIONS', 'ENABLE TRIGGER', 'ENABLE TRIGGER (always)', 'ENABLE TRIGGER (replica)', 'DISABLE TRIGGER', 'ENABLE TRIGGER (all)', 'DISABLE TRIGGER (all)', 'ENABLE TRIGGER (user)', 'DISABLE TRIGGER (user)', 'ENABLE RULE', 'ENABLE RULE (always)', 'ENABLE RULE (replica)', 'DISABLE RULE', 'SET OPTIONS']::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF NOT NEW.ddl_only_replication AND EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND NOT NEW.ddl_only_replication AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs, unless you are using the ddl_only_replication setting. $$, NEW.set_name; END IF; RETURN NEW; END; $function$ ; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_restricted_tags CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND pgl_ddl_deploy.standard_repset_only_tags() @> create_tags AND drop_tags IS NULL)); SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.5--1.6.sql000066400000000000000000000750131453171545300211520ustar00rootroot00000000000000/* pgl_ddl_deploy--1.5--1.6.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.current_query() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgl_ddl_deploy_current_query' LANGUAGE C VOLATILE STRICT; -- Drop UPDATE event for this trigger, which leads to unexpected behavior DROP TRIGGER set_tag_defaults ON pgl_ddl_deploy.set_configs; CREATE TRIGGER set_tag_defaults BEFORE INSERT ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT DISTINCT ON (l.pid) p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid /*** We need to check if this is an inheritance parent, because even a share lock on a child will prevent DDL on parent ***/ LEFT JOIN pg_inherits pi ON pi.inhrelid = c.oid LEFT JOIN pg_class ipc on ipc.oid = pi.inhparent LEFT JOIN pg_namespace ipn on ipn.oid = ipc.relnamespace -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND ((n.nspname = p_nspname AND c.relname = p_relname) OR (ipn.nspname = p_nspname AND ipc.relname = p_relname)) AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY l.pid, a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, REPLACE(p_message,'%','%%')); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT ,"CREATE RULE" ,"CREATE TRIGGER" ,"ALTER TRIGGER"}'::TEXT[]; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||quote_literal(c_provider_name)||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG '%', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; -- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.5.sql000066400000000000000000005761611453171545300206050ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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; /*** pglogical version-specific handling This is not sufficient if pglogical is upgraded underneath an installation of pgl_ddl_deploy, but at least will support either version at install. If you indeed were to do that, you will likely start to see WARNING level logs indicating a problem. DDL statements should not fail. To correct the problem manually, run pgl_ddl_deploy.dependency_update() ****/ CREATE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql; SELECT pgl_ddl_deploy.dependency_update(); CREATE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = '$BUILD$||include_schema_regex||$BUILD$'; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT pgl_ddl_deploy.blacklisted_tags() && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; --Log change on subscriber INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, executed_at, ddl_sql) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex) THEN 1 ELSE 0 END) AS relevant_schema_count INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands(); $BUILD$::TEXT AS shared_objects_check FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT set_name, auto_replication_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) THEN 1 ELSE 0 END) AS relevant_schema_count , SUM(CASE WHEN (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) THEN 1 ELSE 0 END) AS excluded_schema_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'ALTER TABLE' ,'CREATE SEQUENCE' ,'ALTER SEQUENCE' ,'CREATE SCHEMA' ,'CREATE TABLE' ,'CREATE FUNCTION' ,'ALTER FUNCTION' ,'CREATE TYPE' ,'ALTER TYPE' ,'CREATE VIEW' ,'ALTER VIEW') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN( 'DROP SCHEMA' ,'DROP TABLE' ,'DROP FUNCTION' ,'DROP TYPE' ,'DROP VIEW' ,'DROP SEQUENCE') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'CREATE TABLE AS' ,'SELECT INTO' ) --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.set_name, b.auto_replication_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_include_schema_regex TEXT; BEGIN SELECT include_schema_regex INTO c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for set % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||c_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /*** 2 Changes: - This was causing issues due to event triggers firing. Disable via session_replication_role. - We need to re-grant access to the view after dependency_update. ****/ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql SET SESSION_REPLICATION_ROLE TO REPLICA; /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id int, p_set_name text, p_include_schema_regex text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit --These unsupported event triggers could have been erroneously added in v. 1.1 for include_only_repset_table configs SELECT pgl_ddl_deploy.drop_ext_object('EVENT TRIGGER',auto_replication_unsupported_trigger_name), pgl_ddl_deploy.drop_ext_object('FUNCTION',auto_replication_unsupported_function_name||'()') FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables; DO $$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT ets.auto_replication_unsupported_trigger_name, ets.auto_replication_unsupported_function_name FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables LOOP v_sql:='DROP EVENT TRIGGER IF EXISTS '||v_rec.auto_replication_unsupported_trigger_name||'; DROP FUNCTION IF EXISTS '||v_rec.auto_replication_unsupported_function_name||'();'; EXECUTE v_sql; END LOOP; END$$; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; --Need this for unprivileged users to be able to run the function and check if tables are repset tables GRANT SELECT ON TABLE pglogical.replication_set TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; /**** Drop any deployed event triggers for include_only_repset_tables and recreate now with fixed function def. ****/ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed AND include_only_repset_tables; SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; /* pgl_ddl_deploy--1.3--1.4.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; SELECT pgl_ddl_deploy.drop_ext_object('FUNCTION','pgl_ddl_deploy.dependency_update()'); DROP FUNCTION pgl_ddl_deploy.dependency_update(); SELECT pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN exclude_alter_table_subcommands TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_only_alter_table; SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs_id_seq', ''); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN ddl_only_replication BOOLEAN NOT NULL DEFAULT FALSE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table 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 pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id, NULL); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(NULL, p_set_name); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_tag(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_tag' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_type(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_type' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_repset_only_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$'::TEXT; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.common_exclude_alter_table_subcommands() RETURNS TEXT[] AS $BODY$ SELECT ARRAY[ 'ADD CONSTRAINT', 'ADD CONSTRAINT (and recurse)', '(re) ADD CONSTRAINT', 'ALTER CONSTRAINT', 'VALIDATE CONSTRAINT', 'VALIDATE CONSTRAINT (and recurse)', 'ADD (processed) CONSTRAINT', 'ADD CONSTRAINT (using index)', 'DROP CONSTRAINT', 'DROP CONSTRAINT (and recurse)', 'SET LOGGED', 'SET UNLOGGED', 'SET TABLESPACE', 'SET RELOPTIONS', 'RESET RELOPTIONS', 'REPLACE RELOPTIONS', 'ENABLE TRIGGER', 'ENABLE TRIGGER (always)', 'ENABLE TRIGGER (replica)', 'DISABLE TRIGGER', 'ENABLE TRIGGER (all)', 'DISABLE TRIGGER (all)', 'ENABLE TRIGGER (user)', 'DISABLE TRIGGER (user)', 'ENABLE RULE', 'ENABLE RULE (always)', 'ENABLE RULE (replica)', 'DISABLE RULE', 'SET OPTIONS']::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF NOT NEW.ddl_only_replication AND EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND NOT NEW.ddl_only_replication AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs, unless you are using the ddl_only_replication setting. $$, NEW.set_name; END IF; RETURN NEW; END; $function$ ; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_restricted_tags CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND pgl_ddl_deploy.standard_repset_only_tags() @> create_tags AND drop_tags IS NULL)); SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; /* pgl_ddl_deploy--1.4--1.5.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_everything BOOLEAN NOT NULL DEFAULT FALSE; -- Now we have 3 configuration types ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_or_regex_inclusion; -- Only allow one of them to be chosen ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT single_configuration_type CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL) OR (include_everything AND NOT include_only_repset_tables AND include_schema_regex IS NULL)); ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT ddl_only_restrictions CHECK (NOT (ddl_only_replication AND include_only_repset_tables)); -- Need to adjust to after trigger and change function def DROP TRIGGER unique_tags ON pgl_ddl_deploy.set_configs; DROP FUNCTION pgl_ddl_deploy.unique_tags(); -- We need to add the column include_everything to it in a nice order DROP VIEW pgl_ddl_deploy.event_trigger_schema; -- Support canceling or terminating blocking processes on subscriber CREATE TYPE pgl_ddl_deploy.signals AS ENUM ('cancel','terminate','cancel_then_terminate'); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN signal_blocking_subscriber_sessions pgl_ddl_deploy.signals; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN subscriber_lock_timeout INT; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_signal_blocker_config CHECK (NOT (lock_safe_deployment AND (signal_blocking_subscriber_sessions IS NOT NULL OR subscriber_lock_timeout IS NOT NULL)) AND NOT (subscriber_lock_timeout IS NOT NULL AND signal_blocking_subscriber_sessions IS NULL)); CREATE TABLE pgl_ddl_deploy.killed_blockers ( id SERIAL PRIMARY KEY, signal TEXT, successful BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN DEFAULT FALSE, reported_at TIMESTAMPTZ ); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_output TEXT; BEGIN WITH dupes AS ( SELECT set_name, CASE WHEN include_only_repset_tables THEN 'include_only_repset_tables' WHEN include_everything AND NOT ddl_only_replication THEN 'include_everything' WHEN include_schema_regex IS NOT NULL AND NOT ddl_only_replication THEN 'include_schema_regex' WHEN ddl_only_replication THEN CASE WHEN include_everything THEN 'ddl_only_include_everything' WHEN include_schema_regex IS NOT NULL THEN 'ddl_only_include_schema_regex' END END AS category, unnest(array_cat(create_tags, drop_tags)) AS command_tag FROM pgl_ddl_deploy.set_configs GROUP BY 1, 2, 3 HAVING COUNT(1) > 1) , aggregate_dupe_tags AS ( SELECT set_name, category, string_agg(command_tag, ', ' ORDER BY command_tag) AS command_tags FROM dupes GROUP BY 1, 2 ) SELECT string_agg(format('%s: %s: %s', set_name, category, command_tags), ', ') AS output INTO v_output FROM aggregate_dupe_tags; IF v_output IS NOT NULL THEN RAISE EXCEPTION '%', format('You have overlapping configuration types and command tags which is not permitted: %s', v_output); END IF; RETURN NULL; END; $function$ ; CREATE TRIGGER unique_tags AFTER INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND n.nspname = p_nspname AND c.relname = p_relname AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(name, regclass, boolean, text[], text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_set_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, p_message); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||quote_literal(c_provider_name)||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG '%', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.6--1.7.sql000066400000000000000000000032311453171545300211450ustar00rootroot00000000000000/* pgl_ddl_deploy--1.6--1.7.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.6.sql000066400000000000000000006731741453171545300206100ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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; /*** pglogical version-specific handling This is not sufficient if pglogical is upgraded underneath an installation of pgl_ddl_deploy, but at least will support either version at install. If you indeed were to do that, you will likely start to see WARNING level logs indicating a problem. DDL statements should not fail. To correct the problem manually, run pgl_ddl_deploy.dependency_update() ****/ CREATE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql; SELECT pgl_ddl_deploy.dependency_update(); CREATE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = '$BUILD$||include_schema_regex||$BUILD$'; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT pgl_ddl_deploy.blacklisted_tags() && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; --Log change on subscriber INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, executed_at, ddl_sql) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex) THEN 1 ELSE 0 END) AS relevant_schema_count INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands(); $BUILD$::TEXT AS shared_objects_check FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT set_name, auto_replication_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) THEN 1 ELSE 0 END) AS relevant_schema_count , SUM(CASE WHEN (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) THEN 1 ELSE 0 END) AS excluded_schema_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'ALTER TABLE' ,'CREATE SEQUENCE' ,'ALTER SEQUENCE' ,'CREATE SCHEMA' ,'CREATE TABLE' ,'CREATE FUNCTION' ,'ALTER FUNCTION' ,'CREATE TYPE' ,'ALTER TYPE' ,'CREATE VIEW' ,'ALTER VIEW') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN( 'DROP SCHEMA' ,'DROP TABLE' ,'DROP FUNCTION' ,'DROP TYPE' ,'DROP VIEW' ,'DROP SEQUENCE') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'CREATE TABLE AS' ,'SELECT INTO' ) --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.set_name, b.auto_replication_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_include_schema_regex TEXT; BEGIN SELECT include_schema_regex INTO c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for set % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||c_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /*** 2 Changes: - This was causing issues due to event triggers firing. Disable via session_replication_role. - We need to re-grant access to the view after dependency_update. ****/ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql SET SESSION_REPLICATION_ROLE TO REPLICA; /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id int, p_set_name text, p_include_schema_regex text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit --These unsupported event triggers could have been erroneously added in v. 1.1 for include_only_repset_table configs SELECT pgl_ddl_deploy.drop_ext_object('EVENT TRIGGER',auto_replication_unsupported_trigger_name), pgl_ddl_deploy.drop_ext_object('FUNCTION',auto_replication_unsupported_function_name||'()') FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables; DO $$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT ets.auto_replication_unsupported_trigger_name, ets.auto_replication_unsupported_function_name FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables LOOP v_sql:='DROP EVENT TRIGGER IF EXISTS '||v_rec.auto_replication_unsupported_trigger_name||'; DROP FUNCTION IF EXISTS '||v_rec.auto_replication_unsupported_function_name||'();'; EXECUTE v_sql; END LOOP; END$$; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; --Need this for unprivileged users to be able to run the function and check if tables are repset tables GRANT SELECT ON TABLE pglogical.replication_set TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; /**** Drop any deployed event triggers for include_only_repset_tables and recreate now with fixed function def. ****/ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed AND include_only_repset_tables; SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; /* pgl_ddl_deploy--1.3--1.4.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; SELECT pgl_ddl_deploy.drop_ext_object('FUNCTION','pgl_ddl_deploy.dependency_update()'); DROP FUNCTION pgl_ddl_deploy.dependency_update(); SELECT pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN exclude_alter_table_subcommands TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_only_alter_table; SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs_id_seq', ''); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN ddl_only_replication BOOLEAN NOT NULL DEFAULT FALSE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table 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 pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id, NULL); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(NULL, p_set_name); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_tag(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_tag' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_type(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_type' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_repset_only_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$'::TEXT; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.common_exclude_alter_table_subcommands() RETURNS TEXT[] AS $BODY$ SELECT ARRAY[ 'ADD CONSTRAINT', 'ADD CONSTRAINT (and recurse)', '(re) ADD CONSTRAINT', 'ALTER CONSTRAINT', 'VALIDATE CONSTRAINT', 'VALIDATE CONSTRAINT (and recurse)', 'ADD (processed) CONSTRAINT', 'ADD CONSTRAINT (using index)', 'DROP CONSTRAINT', 'DROP CONSTRAINT (and recurse)', 'SET LOGGED', 'SET UNLOGGED', 'SET TABLESPACE', 'SET RELOPTIONS', 'RESET RELOPTIONS', 'REPLACE RELOPTIONS', 'ENABLE TRIGGER', 'ENABLE TRIGGER (always)', 'ENABLE TRIGGER (replica)', 'DISABLE TRIGGER', 'ENABLE TRIGGER (all)', 'DISABLE TRIGGER (all)', 'ENABLE TRIGGER (user)', 'DISABLE TRIGGER (user)', 'ENABLE RULE', 'ENABLE RULE (always)', 'ENABLE RULE (replica)', 'DISABLE RULE', 'SET OPTIONS']::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF NOT NEW.ddl_only_replication AND EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND NOT NEW.ddl_only_replication AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs, unless you are using the ddl_only_replication setting. $$, NEW.set_name; END IF; RETURN NEW; END; $function$ ; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_restricted_tags CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND pgl_ddl_deploy.standard_repset_only_tags() @> create_tags AND drop_tags IS NULL)); SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; /* pgl_ddl_deploy--1.4--1.5.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_everything BOOLEAN NOT NULL DEFAULT FALSE; -- Now we have 3 configuration types ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_or_regex_inclusion; -- Only allow one of them to be chosen ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT single_configuration_type CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL) OR (include_everything AND NOT include_only_repset_tables AND include_schema_regex IS NULL)); ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT ddl_only_restrictions CHECK (NOT (ddl_only_replication AND include_only_repset_tables)); -- Need to adjust to after trigger and change function def DROP TRIGGER unique_tags ON pgl_ddl_deploy.set_configs; DROP FUNCTION pgl_ddl_deploy.unique_tags(); -- We need to add the column include_everything to it in a nice order DROP VIEW pgl_ddl_deploy.event_trigger_schema; -- Support canceling or terminating blocking processes on subscriber CREATE TYPE pgl_ddl_deploy.signals AS ENUM ('cancel','terminate','cancel_then_terminate'); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN signal_blocking_subscriber_sessions pgl_ddl_deploy.signals; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN subscriber_lock_timeout INT; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_signal_blocker_config CHECK (NOT (lock_safe_deployment AND (signal_blocking_subscriber_sessions IS NOT NULL OR subscriber_lock_timeout IS NOT NULL)) AND NOT (subscriber_lock_timeout IS NOT NULL AND signal_blocking_subscriber_sessions IS NULL)); CREATE TABLE pgl_ddl_deploy.killed_blockers ( id SERIAL PRIMARY KEY, signal TEXT, successful BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN DEFAULT FALSE, reported_at TIMESTAMPTZ ); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_output TEXT; BEGIN WITH dupes AS ( SELECT set_name, CASE WHEN include_only_repset_tables THEN 'include_only_repset_tables' WHEN include_everything AND NOT ddl_only_replication THEN 'include_everything' WHEN include_schema_regex IS NOT NULL AND NOT ddl_only_replication THEN 'include_schema_regex' WHEN ddl_only_replication THEN CASE WHEN include_everything THEN 'ddl_only_include_everything' WHEN include_schema_regex IS NOT NULL THEN 'ddl_only_include_schema_regex' END END AS category, unnest(array_cat(create_tags, drop_tags)) AS command_tag FROM pgl_ddl_deploy.set_configs GROUP BY 1, 2, 3 HAVING COUNT(1) > 1) , aggregate_dupe_tags AS ( SELECT set_name, category, string_agg(command_tag, ', ' ORDER BY command_tag) AS command_tags FROM dupes GROUP BY 1, 2 ) SELECT string_agg(format('%s: %s: %s', set_name, category, command_tags), ', ') AS output INTO v_output FROM aggregate_dupe_tags; IF v_output IS NOT NULL THEN RAISE EXCEPTION '%', format('You have overlapping configuration types and command tags which is not permitted: %s', v_output); END IF; RETURN NULL; END; $function$ ; CREATE TRIGGER unique_tags AFTER INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND n.nspname = p_nspname AND c.relname = p_relname AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(name, regclass, boolean, text[], text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_set_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, p_message); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||quote_literal(c_provider_name)||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG '%', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; /* pgl_ddl_deploy--1.5--1.6.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.current_query() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgl_ddl_deploy_current_query' LANGUAGE C VOLATILE STRICT; -- Drop UPDATE event for this trigger, which leads to unexpected behavior DROP TRIGGER set_tag_defaults ON pgl_ddl_deploy.set_configs; CREATE TRIGGER set_tag_defaults BEFORE INSERT ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT DISTINCT ON (l.pid) p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid /*** We need to check if this is an inheritance parent, because even a share lock on a child will prevent DDL on parent ***/ LEFT JOIN pg_inherits pi ON pi.inhrelid = c.oid LEFT JOIN pg_class ipc on ipc.oid = pi.inhparent LEFT JOIN pg_namespace ipn on ipn.oid = ipc.relnamespace -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND ((n.nspname = p_nspname AND c.relname = p_relname) OR (ipn.nspname = p_nspname AND ipc.relname = p_relname)) AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY l.pid, a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, REPLACE(p_message,'%','%%')); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT ,"CREATE RULE" ,"CREATE TRIGGER" ,"ALTER TRIGGER"}'::TEXT[]; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||quote_literal(c_provider_name)||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG '%', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; -- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.7--2.0.sql000066400000000000000000001465621453171545300211570ustar00rootroot00000000000000/* pgl_ddl_deploy--1.7--2.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_views WHERE schemaname = 'pgl_ddl_deploy' AND viewname = 'event_trigger_schema') THEN DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; ELSE DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT NULL::INT AS id; END IF; END$$; CREATE TYPE pgl_ddl_deploy.driver AS ENUM ('pglogical', 'native'); -- Not possible that any existing config would be native, so: ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN driver pgl_ddl_deploy.driver NOT NULL DEFAULT 'pglogical'; DROP FUNCTION IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper(); DROP FUNCTION IF EXISTS pgl_ddl_deploy.deployment_check_count(integer, text, text); DROP FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN ); CREATE TABLE pgl_ddl_deploy.queue( queued_at timestamp with time zone not null, role name not null, pubnames text[], message_type "char" not null, message text not null ); COMMENT ON TABLE pgl_ddl_deploy.queue IS 'Modeled on the pglogical.queue table for native logical replication ddl'; ALTER TABLE pgl_ddl_deploy.queue REPLICA IDENTITY FULL; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; -- NOTE - this duplicates execute_queued_ddl.sql function file but is executed here for the upgrade/build path CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; CREATE TRIGGER execute_queued_ddl BEFORE INSERT ON pgl_ddl_deploy.queue FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.execute_queued_ddl(); -- This must only fire on the replica ALTER TABLE pgl_ddl_deploy.queue ENABLE REPLICA TRIGGER execute_queued_ddl; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.replicate_ddl_command(command text, pubnames text[]) RETURNS BOOLEAN LANGUAGE plpgsql AS $function$ -- Modeled after pglogical's replicate_ddl_command but in support of native logical replication BEGIN -- NOTE: pglogical uses clock_timestamp() to log queued_at times and we do the same here INSERT INTO pgl_ddl_deploy.queue (queued_at, role, pubnames, message_type, message) VALUES (clock_timestamp(), current_role, pubnames, pgl_ddl_deploy.queue_ddl_message_type(), command); RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_table_to_replication(p_driver pgl_ddl_deploy.driver, p_set_name name, p_relation regclass, p_synchronize_data boolean DEFAULT false) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_schema NAME; v_table NAME; v_result BOOLEAN = false; BEGIN IF p_driver = 'pglogical' THEN SELECT pglogical.replication_set_add_table( set_name:=p_set_name ,relation:=p_relation ,synchronize_data:=p_synchronize_data ) INTO v_result; ELSEIF p_driver = 'native' THEN SELECT nspname, relname INTO v_schema, v_table FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.oid = p_relation::OID; EXECUTE 'ALTER PUBLICATION '||quote_ident(p_set_name)||' ADD TABLE '||quote_ident(v_schema)||'.'||quote_ident(v_table)||';'; -- We use true to synchronize data here, not taking the value from p_synchronize_data. This is because of the different way -- that native logical works, and that changes are not queued from the time of the table being added to replication. Thus, we -- by default WILL use COPY_DATA = true -- This needs to be in a DO block currently because of how the DDL is processed on the subscriber. PERFORM pgl_ddl_deploy.replicate_ddl_command($$DO $AUTO_REPLICATE_BLOCK$ BEGIN PERFORM pgl_ddl_deploy.notify_subscription_refresh('$$||p_set_name||$$', true); END$AUTO_REPLICATE_BLOCK$;$$, array[p_set_name]); v_result = true; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; RETURN v_result; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.notify_subscription_refresh(p_set_name name, p_copy_data boolean DEFAULT TRUE) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pg_subscription WHERE subpublications && array[p_set_name::text]) THEN RAISE EXCEPTION 'No subscription to publication % exists', p_set_name; END IF; FOR v_rec IN SELECT unnest(subpublications) AS pubname, subname FROM pg_subscription WHERE subpublications && array[p_set_name::text] LOOP v_sql = $$ALTER SUBSCRIPTION $$||quote_ident(v_rec.subname)||$$ REFRESH PUBLICATION WITH ( COPY_DATA = '$$||p_copy_data||$$');$$; RAISE LOG 'pgl_ddl_deploy executing: %', v_sql; EXECUTE v_sql; END LOOP; RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (id OID, relid REGCLASS, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id); ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id); ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; END IF; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_wrapper() RETURNS TABLE (id OID, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs; ELSE RAISE EXCEPTION 'pglogical required for version prior to Postgres 10'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs UNION ALL SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSE RAISE EXCEPTION 'Unexpected exception'; END IF; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text, p_driver pgl_ddl_deploy.driver) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = p_set_name AND rsr.relid = c.oid AND rsr.driver = p_driver); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = '$SQL$||p_set_name||$SQL$' AND rsr.relid = c.oid AND rsr.driver = (SELECT driver FROM pgl_ddl_deploy.set_configs WHERE set_name = '$SQL$||p_set_name||$SQL$')); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; v_driver pgl_ddl_deploy.driver; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication, driver INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication, v_driver FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex, v_driver); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.is_subscriber(p_driver pgl_ddl_deploy.driver, p_name TEXT[], p_provider_name NAME = NULL) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN IF p_driver = 'pglogical' THEN RETURN EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_name); ELSEIF p_driver = 'native' THEN RETURN EXISTS (SELECT 1 FROM pg_subscription s WHERE subpublications && p_name); ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, p_driver pgl_ddl_deploy.driver, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN IF pgl_ddl_deploy.is_subscriber(p_driver, p_set_name, p_provider_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.queue_ddl_message_type() RETURNS "char" LANGUAGE sql IMMUTABLE AS $function$ SELECT 'Q'::"char"; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.provider_node_name(p_driver pgl_ddl_deploy.driver) RETURNS NAME LANGUAGE plpgsql AS $function$ DECLARE v_node_name NAME; BEGIN IF p_driver = 'pglogical' THEN SELECT n.node_name INTO v_node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id); RETURN v_node_name; ELSEIF p_driver = 'native' THEN RETURN NULL::NAME; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT sc.id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, sc.driver, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||sc.id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_driver pgl_ddl_deploy.driver = '$BUILD$||sc.driver||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=pgl_ddl_deploy.provider_node_name(c_driver); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; RAISE DEBUG 'v_full_ddl: %', v_full_ddl; RAISE DEBUG 'c_set_config_id: %', c_set_config_id; RAISE DEBUG 'c_set_name: %', c_set_name; RAISE DEBUG 'c_driver: %', c_driver; RAISE DEBUG 'v_ddl_sql_sent: %', v_ddl_sql_sent; v_sql:=$INNER_BLOCK$ SELECT $BUILD$||CASE WHEN sc.driver = 'native' THEN 'pgl_ddl_deploy' WHEN sc.driver = 'pglogical' THEN 'pglogical' ELSE 'ERROR-EXCEPTION' END||$BUILD$.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||COALESCE(quote_literal(c_provider_name), 'NULL')||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$, p_driver := $INNER_BLOCK$||quote_literal(c_driver)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG 'v_sql: %', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = c_set_name AND rsr.relid = c.oid AND rsr.driver = c_driver) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.relid = c.objid AND c.object_type in('table','table column','table constraint') AND rsr.name = '$BUILD$||sc.set_name||$BUILD$' AND rsr.driver = '$BUILD$||sc.driver||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pgl_ddl_deploy.rep_set_wrapper() rs INNER JOIN pgl_ddl_deploy.set_configs sc ON sc.set_name = rs.name AND sc.driver = rs.driver ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN driver = 'pglogical' THEN '--no-op pglogical diver'::TEXT WHEN driver = 'native' THEN $BUILD$ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname = '$BUILD$||set_name||$BUILD$' AND schemaname = 'pgl_ddl_deploy' AND tablename = 'queue') THEN ALTER PUBLICATION $BUILD$||quote_ident(set_name)||$BUILD$ ADD TABLE pgl_ddl_deploy.queue; END IF; END$$; $BUILD$ END AS add_queue_table_to_replication, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pgl_ddl_deploy.add_table_to_replication( p_driver:=c_driver ,p_set_name:=c_set_name ,p_relation:=c.oid ,p_synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| b.add_queue_table_to_replication||$BUILD$ $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||';'; EXECUTE v_sql; IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; END IF; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; -- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE IF EXISTS ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) FROM PUBLIC; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--1.7.sql000066400000000000000000006736031453171545300206060ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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; /*** pglogical version-specific handling This is not sufficient if pglogical is upgraded underneath an installation of pgl_ddl_deploy, but at least will support either version at install. If you indeed were to do that, you will likely start to see WARNING level logs indicating a problem. DDL statements should not fail. To correct the problem manually, run pgl_ddl_deploy.dependency_update() ****/ CREATE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql; SELECT pgl_ddl_deploy.dependency_update(); CREATE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = '$BUILD$||include_schema_regex||$BUILD$'; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT pgl_ddl_deploy.blacklisted_tags() && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; --Log change on subscriber INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, executed_at, ddl_sql) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex) THEN 1 ELSE 0 END) AS relevant_schema_count INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands(); $BUILD$::TEXT AS shared_objects_check FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT set_name, auto_replication_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if table being altered is in a relevant schema */ SELECT COUNT(1) , SUM(CASE WHEN schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) THEN 1 ELSE 0 END) AS relevant_schema_count , SUM(CASE WHEN (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) THEN 1 ELSE 0 END) AS excluded_schema_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN $BUILD$||shared_objects_check||$BUILD$ IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'ALTER TABLE' ,'CREATE SEQUENCE' ,'ALTER SEQUENCE' ,'CREATE SCHEMA' ,'CREATE TABLE' ,'CREATE FUNCTION' ,'ALTER FUNCTION' ,'CREATE TYPE' ,'ALTER TYPE' ,'CREATE VIEW' ,'ALTER VIEW') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN( 'DROP SCHEMA' ,'DROP TABLE' ,'DROP FUNCTION' ,'DROP TYPE' ,'DROP VIEW' ,'DROP SEQUENCE') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN( 'CREATE TABLE AS' ,'SELECT INTO' ) --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.set_name, b.auto_replication_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_include_schema_regex TEXT; BEGIN SELECT include_schema_regex INTO c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for set % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||c_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /*** 2 Changes: - This was causing issues due to event triggers firing. Disable via session_replication_role. - We need to re-grant access to the view after dependency_update. ****/ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.dependency_update() RETURNS VOID AS $DEPS$ DECLARE v_sql TEXT; v_rep_set_add_table TEXT; BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'rep_set_table_wrapper' AND table_schema = 'pgl_ddl_deploy') THEN PERFORM pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW pgl_ddl_deploy.rep_set_table_wrapper; END IF; IF (SELECT extversion FROM pg_extension WHERE extname = 'pglogical') ~* '^1.*' THEN CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_relation; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean)'; ELSE CREATE VIEW pgl_ddl_deploy.rep_set_table_wrapper AS SELECT * FROM pglogical.replication_set_table; v_rep_set_add_table = 'pglogical.replication_set_add_table(name, regclass, boolean, text[], text)'; END IF; GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; v_sql:=$$ CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS BOOLEAN AS $BODY$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION $$||v_rep_set_add_table||$$ TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $BODY$ LANGUAGE plpgsql; $$; EXECUTE v_sql; END; $DEPS$ LANGUAGE plpgsql SET SESSION_REPLICATION_ROLE TO REPLICA; /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id int, p_set_name text, p_include_schema_regex text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit --These unsupported event triggers could have been erroneously added in v. 1.1 for include_only_repset_table configs SELECT pgl_ddl_deploy.drop_ext_object('EVENT TRIGGER',auto_replication_unsupported_trigger_name), pgl_ddl_deploy.drop_ext_object('FUNCTION',auto_replication_unsupported_function_name||'()') FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables; DO $$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT ets.auto_replication_unsupported_trigger_name, ets.auto_replication_unsupported_function_name FROM pgl_ddl_deploy.event_trigger_schema ets INNER JOIN pgl_ddl_deploy.set_configs sc USING (id) WHERE include_only_repset_tables LOOP v_sql:='DROP EVENT TRIGGER IF EXISTS '||v_rec.auto_replication_unsupported_trigger_name||'; DROP FUNCTION IF EXISTS '||v_rec.auto_replication_unsupported_function_name||'();'; EXECUTE v_sql; END LOOP; END$$; ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type = 'table' AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger FROM vars) SELECT b.id, b.set_name, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql FROM build b; --Just do this to avoid unneeded complexity with dependency_update GRANT SELECT ON TABLE pgl_ddl_deploy.rep_set_table_wrapper TO PUBLIC; --Need this for unprivileged users to be able to run the function and check if tables are repset tables GRANT SELECT ON TABLE pglogical.replication_set TO PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER EXTENSION pgl_ddl_deploy DROP VIEW pgl_ddl_deploy.event_trigger_schema; DROP VIEW pgl_ddl_deploy.event_trigger_schema; CREATE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$||c_search_path||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; /**** Drop any deployed event triggers for include_only_repset_tables and recreate now with fixed function def. ****/ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed AND include_only_repset_tables; SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; /* pgl_ddl_deploy--1.3--1.4.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; SELECT pgl_ddl_deploy.drop_ext_object('FUNCTION','pgl_ddl_deploy.dependency_update()'); DROP FUNCTION pgl_ddl_deploy.dependency_update(); SELECT pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN exclude_alter_table_subcommands TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_only_alter_table; SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs_id_seq', ''); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN ddl_only_replication BOOLEAN NOT NULL DEFAULT FALSE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table 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 pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id, NULL); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(NULL, p_set_name); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = p_set_name AND rsr.set_reloid = c.oid); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = '$SQL$||p_set_name||$SQL$' AND rsr.set_reloid = c.oid); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE $EXEC_SUBSCRIBER$ $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ $EXEC_SUBSCRIBER$; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ DO $AUTO_REPLICATE_BLOCK$ DECLARE c_queue_subscriber_failures BOOLEAN = $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$; v_succeeded BOOLEAN; v_error_message TEXT; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$' WHERE sub_replication_sets && ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']) THEN v_error_message = NULL; BEGIN --Execute DDL $INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$ v_succeeded = TRUE; EXCEPTION WHEN OTHERS THEN IF c_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; ELSE RAISE; END IF; END; INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('$INNER_BLOCK$||c_set_name||$INNER_BLOCK$', $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, '$INNER_BLOCK$||c_provider_name||$INNER_BLOCK$', $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, current_role, pg_backend_pid(), current_timestamp, $SQL$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$SQL$, $SQL$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$SQL$, v_succeeded, v_error_message); END IF; END$AUTO_REPLICATE_BLOCK$; $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, SQLERRM, SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_cmd_count = v_match_count) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF (v_match_count > 0 AND v_excluded_count = 0) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count FROM pg_event_trigger_ddl_commands() c; IF v_match_count > 0 THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_tag(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_tag' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_type(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_type' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_repset_only_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$'::TEXT; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.common_exclude_alter_table_subcommands() RETURNS TEXT[] AS $BODY$ SELECT ARRAY[ 'ADD CONSTRAINT', 'ADD CONSTRAINT (and recurse)', '(re) ADD CONSTRAINT', 'ALTER CONSTRAINT', 'VALIDATE CONSTRAINT', 'VALIDATE CONSTRAINT (and recurse)', 'ADD (processed) CONSTRAINT', 'ADD CONSTRAINT (using index)', 'DROP CONSTRAINT', 'DROP CONSTRAINT (and recurse)', 'SET LOGGED', 'SET UNLOGGED', 'SET TABLESPACE', 'SET RELOPTIONS', 'RESET RELOPTIONS', 'REPLACE RELOPTIONS', 'ENABLE TRIGGER', 'ENABLE TRIGGER (always)', 'ENABLE TRIGGER (replica)', 'DISABLE TRIGGER', 'ENABLE TRIGGER (all)', 'DISABLE TRIGGER (all)', 'ENABLE TRIGGER (user)', 'DISABLE TRIGGER (user)', 'ENABLE RULE', 'ENABLE RULE (always)', 'ENABLE RULE (replica)', 'DISABLE RULE', 'SET OPTIONS']::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF NOT NEW.ddl_only_replication AND EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND NOT NEW.ddl_only_replication AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs, unless you are using the ddl_only_replication setting. $$, NEW.set_name; END IF; RETURN NEW; END; $function$ ; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_restricted_tags CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND pgl_ddl_deploy.standard_repset_only_tags() @> create_tags AND drop_tags IS NULL)); SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; /* pgl_ddl_deploy--1.4--1.5.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_everything BOOLEAN NOT NULL DEFAULT FALSE; -- Now we have 3 configuration types ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_or_regex_inclusion; -- Only allow one of them to be chosen ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT single_configuration_type CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL) OR (include_everything AND NOT include_only_repset_tables AND include_schema_regex IS NULL)); ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT ddl_only_restrictions CHECK (NOT (ddl_only_replication AND include_only_repset_tables)); -- Need to adjust to after trigger and change function def DROP TRIGGER unique_tags ON pgl_ddl_deploy.set_configs; DROP FUNCTION pgl_ddl_deploy.unique_tags(); -- We need to add the column include_everything to it in a nice order DROP VIEW pgl_ddl_deploy.event_trigger_schema; -- Support canceling or terminating blocking processes on subscriber CREATE TYPE pgl_ddl_deploy.signals AS ENUM ('cancel','terminate','cancel_then_terminate'); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN signal_blocking_subscriber_sessions pgl_ddl_deploy.signals; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN subscriber_lock_timeout INT; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_signal_blocker_config CHECK (NOT (lock_safe_deployment AND (signal_blocking_subscriber_sessions IS NOT NULL OR subscriber_lock_timeout IS NOT NULL)) AND NOT (subscriber_lock_timeout IS NOT NULL AND signal_blocking_subscriber_sessions IS NULL)); CREATE TABLE pgl_ddl_deploy.killed_blockers ( id SERIAL PRIMARY KEY, signal TEXT, successful BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN DEFAULT FALSE, reported_at TIMESTAMPTZ ); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_output TEXT; BEGIN WITH dupes AS ( SELECT set_name, CASE WHEN include_only_repset_tables THEN 'include_only_repset_tables' WHEN include_everything AND NOT ddl_only_replication THEN 'include_everything' WHEN include_schema_regex IS NOT NULL AND NOT ddl_only_replication THEN 'include_schema_regex' WHEN ddl_only_replication THEN CASE WHEN include_everything THEN 'ddl_only_include_everything' WHEN include_schema_regex IS NOT NULL THEN 'ddl_only_include_schema_regex' END END AS category, unnest(array_cat(create_tags, drop_tags)) AS command_tag FROM pgl_ddl_deploy.set_configs GROUP BY 1, 2, 3 HAVING COUNT(1) > 1) , aggregate_dupe_tags AS ( SELECT set_name, category, string_agg(command_tag, ', ' ORDER BY command_tag) AS command_tags FROM dupes GROUP BY 1, 2 ) SELECT string_agg(format('%s: %s: %s', set_name, category, command_tags), ', ') AS output INTO v_output FROM aggregate_dupe_tags; IF v_output IS NOT NULL THEN RAISE EXCEPTION '%', format('You have overlapping configuration types and command tags which is not permitted: %s', v_output); END IF; RETURN NULL; END; $function$ ; CREATE TRIGGER unique_tags AFTER INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND n.nspname = p_nspname AND c.relname = p_relname AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(name, regclass, boolean, text[], text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_set_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, p_message); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||quote_literal(c_provider_name)||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG '%', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; /* pgl_ddl_deploy--1.5--1.6.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.current_query() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgl_ddl_deploy_current_query' LANGUAGE C VOLATILE STRICT; -- Drop UPDATE event for this trigger, which leads to unexpected behavior DROP TRIGGER set_tag_defaults ON pgl_ddl_deploy.set_configs; CREATE TRIGGER set_tag_defaults BEFORE INSERT ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT DISTINCT ON (l.pid) p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid /*** We need to check if this is an inheritance parent, because even a share lock on a child will prevent DDL on parent ***/ LEFT JOIN pg_inherits pi ON pi.inhrelid = c.oid LEFT JOIN pg_class ipc on ipc.oid = pi.inhparent LEFT JOIN pg_namespace ipn on ipn.oid = ipc.relnamespace -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND ((n.nspname = p_nspname AND c.relname = p_relname) OR (ipn.nspname = p_nspname AND ipc.relname = p_relname)) AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY l.pid, a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, REPLACE(p_message,'%','%%')); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT ,"CREATE RULE" ,"CREATE TRIGGER" ,"ALTER TRIGGER"}'::TEXT[]; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=(SELECT n.node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id)); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; v_sql:=$INNER_BLOCK$ SELECT pglogical.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||quote_literal(c_provider_name)||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG '%', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set r ON r.set_id = rsr.set_id WHERE r.set_name = c_set_name AND rsr.set_reloid = c.oid) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr INNER JOIN pglogical.replication_set rs USING (set_id) WHERE rsr.set_reloid = c.objid AND c.object_type in('table','table column','table constraint') AND rs.set_name = '$BUILD$||set_name||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pglogical.replication_set rs INNER JOIN pgl_ddl_deploy.set_configs sc USING (set_name) ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pglogical.replication_set_add_table( set_name:=c_set_name ,relation:=c.oid ,synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; -- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; /* pgl_ddl_deploy--1.6--1.7.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--2.0--2.1.sql000066400000000000000000000041331453171545300211350ustar00rootroot00000000000000/* pgl_ddl_deploy--2.0--2.1.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ -- This handles potential duplicates with multiple subscriptions to same publisher db. IF EXISTS ( SELECT NEW.* INTERSECT SELECT * FROM pgl_ddl_deploy.queue) THEN RETURN NULL; END IF; IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--2.0.sql000066400000000000000000003201641453171545300205670ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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 pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; --Just do this to avoid unneeded complexity with dependency_update -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN exclude_alter_table_subcommands TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_only_alter_table; SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs_id_seq', ''); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN ddl_only_replication BOOLEAN NOT NULL DEFAULT FALSE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table 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 pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id, NULL); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(NULL, p_set_name); END; $function$; CREATE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_tag(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_tag' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_type(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_type' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_repset_only_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$'::TEXT; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.common_exclude_alter_table_subcommands() RETURNS TEXT[] AS $BODY$ SELECT ARRAY[ 'ADD CONSTRAINT', 'ADD CONSTRAINT (and recurse)', '(re) ADD CONSTRAINT', 'ALTER CONSTRAINT', 'VALIDATE CONSTRAINT', 'VALIDATE CONSTRAINT (and recurse)', 'ADD (processed) CONSTRAINT', 'ADD CONSTRAINT (using index)', 'DROP CONSTRAINT', 'DROP CONSTRAINT (and recurse)', 'SET LOGGED', 'SET UNLOGGED', 'SET TABLESPACE', 'SET RELOPTIONS', 'RESET RELOPTIONS', 'REPLACE RELOPTIONS', 'ENABLE TRIGGER', 'ENABLE TRIGGER (always)', 'ENABLE TRIGGER (replica)', 'DISABLE TRIGGER', 'ENABLE TRIGGER (all)', 'DISABLE TRIGGER (all)', 'ENABLE TRIGGER (user)', 'DISABLE TRIGGER (user)', 'ENABLE RULE', 'ENABLE RULE (always)', 'ENABLE RULE (replica)', 'DISABLE RULE', 'SET OPTIONS']::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF NOT NEW.ddl_only_replication AND EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND NOT NEW.ddl_only_replication AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs, unless you are using the ddl_only_replication setting. $$, NEW.set_name; END IF; RETURN NEW; END; $function$ ; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_restricted_tags CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND pgl_ddl_deploy.standard_repset_only_tags() @> create_tags AND drop_tags IS NULL)); /* pgl_ddl_deploy--1.4--1.5.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_everything BOOLEAN NOT NULL DEFAULT FALSE; -- Now we have 3 configuration types ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_or_regex_inclusion; -- Only allow one of them to be chosen ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT single_configuration_type CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL) OR (include_everything AND NOT include_only_repset_tables AND include_schema_regex IS NULL)); ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT ddl_only_restrictions CHECK (NOT (ddl_only_replication AND include_only_repset_tables)); -- Need to adjust to after trigger and change function def DROP TRIGGER unique_tags ON pgl_ddl_deploy.set_configs; DROP FUNCTION pgl_ddl_deploy.unique_tags(); -- Support canceling or terminating blocking processes on subscriber CREATE TYPE pgl_ddl_deploy.signals AS ENUM ('cancel','terminate','cancel_then_terminate'); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN signal_blocking_subscriber_sessions pgl_ddl_deploy.signals; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN subscriber_lock_timeout INT; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_signal_blocker_config CHECK (NOT (lock_safe_deployment AND (signal_blocking_subscriber_sessions IS NOT NULL OR subscriber_lock_timeout IS NOT NULL)) AND NOT (subscriber_lock_timeout IS NOT NULL AND signal_blocking_subscriber_sessions IS NULL)); CREATE TABLE pgl_ddl_deploy.killed_blockers ( id SERIAL PRIMARY KEY, signal TEXT, successful BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN DEFAULT FALSE, reported_at TIMESTAMPTZ ); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_output TEXT; BEGIN WITH dupes AS ( SELECT set_name, CASE WHEN include_only_repset_tables THEN 'include_only_repset_tables' WHEN include_everything AND NOT ddl_only_replication THEN 'include_everything' WHEN include_schema_regex IS NOT NULL AND NOT ddl_only_replication THEN 'include_schema_regex' WHEN ddl_only_replication THEN CASE WHEN include_everything THEN 'ddl_only_include_everything' WHEN include_schema_regex IS NOT NULL THEN 'ddl_only_include_schema_regex' END END AS category, unnest(array_cat(create_tags, drop_tags)) AS command_tag FROM pgl_ddl_deploy.set_configs GROUP BY 1, 2, 3 HAVING COUNT(1) > 1) , aggregate_dupe_tags AS ( SELECT set_name, category, string_agg(command_tag, ', ' ORDER BY command_tag) AS command_tags FROM dupes GROUP BY 1, 2 ) SELECT string_agg(format('%s: %s: %s', set_name, category, command_tags), ', ') AS output INTO v_output FROM aggregate_dupe_tags; IF v_output IS NOT NULL THEN RAISE EXCEPTION '%', format('You have overlapping configuration types and command tags which is not permitted: %s', v_output); END IF; RETURN NULL; END; $function$ ; CREATE TRIGGER unique_tags AFTER INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND n.nspname = p_nspname AND c.relname = p_relname AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(name, regclass, boolean, text[], text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_set_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, p_message); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $function$ ; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; /* pgl_ddl_deploy--1.5--1.6.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.current_query() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgl_ddl_deploy_current_query' LANGUAGE C VOLATILE STRICT; -- Drop UPDATE event for this trigger, which leads to unexpected behavior DROP TRIGGER set_tag_defaults ON pgl_ddl_deploy.set_configs; CREATE TRIGGER set_tag_defaults BEFORE INSERT ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT DISTINCT ON (l.pid) p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid /*** We need to check if this is an inheritance parent, because even a share lock on a child will prevent DDL on parent ***/ LEFT JOIN pg_inherits pi ON pi.inhrelid = c.oid LEFT JOIN pg_class ipc on ipc.oid = pi.inhparent LEFT JOIN pg_namespace ipn on ipn.oid = ipc.relnamespace -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND ((n.nspname = p_nspname AND c.relname = p_relname) OR (ipn.nspname = p_nspname AND ipc.relname = p_relname)) AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY l.pid, a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, REPLACE(p_message,'%','%%')); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT ,"CREATE RULE" ,"CREATE TRIGGER" ,"ALTER TRIGGER"}'::TEXT[]; $function$ ; /* pgl_ddl_deploy--1.6--1.7.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; /* pgl_ddl_deploy--1.7--2.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_views WHERE schemaname = 'pgl_ddl_deploy' AND viewname = 'event_trigger_schema') THEN DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; ELSE DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT NULL::INT AS id; END IF; END$$; CREATE TYPE pgl_ddl_deploy.driver AS ENUM ('pglogical', 'native'); -- Not possible that any existing config would be native, so: ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN driver pgl_ddl_deploy.driver NOT NULL DEFAULT 'pglogical'; DROP FUNCTION IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper(); DROP FUNCTION IF EXISTS pgl_ddl_deploy.deployment_check_count(integer, text, text); DROP FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN ); CREATE TABLE pgl_ddl_deploy.queue( queued_at timestamp with time zone not null, role name not null, pubnames text[], message_type "char" not null, message text not null ); COMMENT ON TABLE pgl_ddl_deploy.queue IS 'Modeled on the pglogical.queue table for native logical replication ddl'; ALTER TABLE pgl_ddl_deploy.queue REPLICA IDENTITY FULL; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; -- NOTE - this duplicates execute_queued_ddl.sql function file but is executed here for the upgrade/build path CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; CREATE TRIGGER execute_queued_ddl BEFORE INSERT ON pgl_ddl_deploy.queue FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.execute_queued_ddl(); -- This must only fire on the replica ALTER TABLE pgl_ddl_deploy.queue ENABLE REPLICA TRIGGER execute_queued_ddl; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.replicate_ddl_command(command text, pubnames text[]) RETURNS BOOLEAN LANGUAGE plpgsql AS $function$ -- Modeled after pglogical's replicate_ddl_command but in support of native logical replication BEGIN -- NOTE: pglogical uses clock_timestamp() to log queued_at times and we do the same here INSERT INTO pgl_ddl_deploy.queue (queued_at, role, pubnames, message_type, message) VALUES (clock_timestamp(), current_role, pubnames, pgl_ddl_deploy.queue_ddl_message_type(), command); RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_table_to_replication(p_driver pgl_ddl_deploy.driver, p_set_name name, p_relation regclass, p_synchronize_data boolean DEFAULT false) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_schema NAME; v_table NAME; v_result BOOLEAN = false; BEGIN IF p_driver = 'pglogical' THEN SELECT pglogical.replication_set_add_table( set_name:=p_set_name ,relation:=p_relation ,synchronize_data:=p_synchronize_data ) INTO v_result; ELSEIF p_driver = 'native' THEN SELECT nspname, relname INTO v_schema, v_table FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.oid = p_relation::OID; EXECUTE 'ALTER PUBLICATION '||quote_ident(p_set_name)||' ADD TABLE '||quote_ident(v_schema)||'.'||quote_ident(v_table)||';'; -- We use true to synchronize data here, not taking the value from p_synchronize_data. This is because of the different way -- that native logical works, and that changes are not queued from the time of the table being added to replication. Thus, we -- by default WILL use COPY_DATA = true -- This needs to be in a DO block currently because of how the DDL is processed on the subscriber. PERFORM pgl_ddl_deploy.replicate_ddl_command($$DO $AUTO_REPLICATE_BLOCK$ BEGIN PERFORM pgl_ddl_deploy.notify_subscription_refresh('$$||p_set_name||$$', true); END$AUTO_REPLICATE_BLOCK$;$$, array[p_set_name]); v_result = true; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; RETURN v_result; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.notify_subscription_refresh(p_set_name name, p_copy_data boolean DEFAULT TRUE) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pg_subscription WHERE subpublications && array[p_set_name::text]) THEN RAISE EXCEPTION 'No subscription to publication % exists', p_set_name; END IF; FOR v_rec IN SELECT unnest(subpublications) AS pubname, subname FROM pg_subscription WHERE subpublications && array[p_set_name::text] LOOP v_sql = $$ALTER SUBSCRIPTION $$||quote_ident(v_rec.subname)||$$ REFRESH PUBLICATION WITH ( COPY_DATA = '$$||p_copy_data||$$');$$; RAISE LOG 'pgl_ddl_deploy executing: %', v_sql; EXECUTE v_sql; END LOOP; RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (id OID, relid REGCLASS, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id); ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id); ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; END IF; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_wrapper() RETURNS TABLE (id OID, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs; ELSE RAISE EXCEPTION 'pglogical required for version prior to Postgres 10'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs UNION ALL SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSE RAISE EXCEPTION 'Unexpected exception'; END IF; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text, p_driver pgl_ddl_deploy.driver) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = p_set_name AND rsr.relid = c.oid AND rsr.driver = p_driver); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = '$SQL$||p_set_name||$SQL$' AND rsr.relid = c.oid AND rsr.driver = (SELECT driver FROM pgl_ddl_deploy.set_configs WHERE set_name = '$SQL$||p_set_name||$SQL$')); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; v_driver pgl_ddl_deploy.driver; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication, driver INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication, v_driver FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex, v_driver); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.is_subscriber(p_driver pgl_ddl_deploy.driver, p_name TEXT[], p_provider_name NAME = NULL) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN IF p_driver = 'pglogical' THEN RETURN EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_name); ELSEIF p_driver = 'native' THEN RETURN EXISTS (SELECT 1 FROM pg_subscription s WHERE subpublications && p_name); ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, p_driver pgl_ddl_deploy.driver, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN IF pgl_ddl_deploy.is_subscriber(p_driver, p_set_name, p_provider_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.queue_ddl_message_type() RETURNS "char" LANGUAGE sql IMMUTABLE AS $function$ SELECT 'Q'::"char"; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.provider_node_name(p_driver pgl_ddl_deploy.driver) RETURNS NAME LANGUAGE plpgsql AS $function$ DECLARE v_node_name NAME; BEGIN IF p_driver = 'pglogical' THEN SELECT n.node_name INTO v_node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id); RETURN v_node_name; ELSEIF p_driver = 'native' THEN RETURN NULL::NAME; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT sc.id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, sc.driver, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||sc.id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_driver pgl_ddl_deploy.driver = '$BUILD$||sc.driver||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=pgl_ddl_deploy.provider_node_name(c_driver); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; RAISE DEBUG 'v_full_ddl: %', v_full_ddl; RAISE DEBUG 'c_set_config_id: %', c_set_config_id; RAISE DEBUG 'c_set_name: %', c_set_name; RAISE DEBUG 'c_driver: %', c_driver; RAISE DEBUG 'v_ddl_sql_sent: %', v_ddl_sql_sent; v_sql:=$INNER_BLOCK$ SELECT $BUILD$||CASE WHEN sc.driver = 'native' THEN 'pgl_ddl_deploy' WHEN sc.driver = 'pglogical' THEN 'pglogical' ELSE 'ERROR-EXCEPTION' END||$BUILD$.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||COALESCE(quote_literal(c_provider_name), 'NULL')||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$, p_driver := $INNER_BLOCK$||quote_literal(c_driver)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG 'v_sql: %', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = c_set_name AND rsr.relid = c.oid AND rsr.driver = c_driver) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.relid = c.objid AND c.object_type in('table','table column','table constraint') AND rsr.name = '$BUILD$||sc.set_name||$BUILD$' AND rsr.driver = '$BUILD$||sc.driver||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pgl_ddl_deploy.rep_set_wrapper() rs INNER JOIN pgl_ddl_deploy.set_configs sc ON sc.set_name = rs.name AND sc.driver = rs.driver ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN driver = 'pglogical' THEN '--no-op pglogical diver'::TEXT WHEN driver = 'native' THEN $BUILD$ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname = '$BUILD$||set_name||$BUILD$' AND schemaname = 'pgl_ddl_deploy' AND tablename = 'queue') THEN ALTER PUBLICATION $BUILD$||quote_ident(set_name)||$BUILD$ ADD TABLE pgl_ddl_deploy.queue; END IF; END$$; $BUILD$ END AS add_queue_table_to_replication, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pgl_ddl_deploy.add_table_to_replication( p_driver:=c_driver ,p_set_name:=c_set_name ,p_relation:=c.oid ,p_synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| b.add_queue_table_to_replication||$BUILD$ $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||';'; EXECUTE v_sql; IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; END IF; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; -- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE IF EXISTS ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) FROM PUBLIC; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--2.1--2.2.sql000066400000000000000000001100371453171545300211400ustar00rootroot00000000000000/* pgl_ddl_deploy--2.1--2.2.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_views WHERE schemaname = 'pgl_ddl_deploy' AND viewname = 'event_trigger_schema') THEN DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; -- it needs to be modified, so now we drop it to recreate later DROP VIEW pgl_ddl_deploy.event_trigger_schema; ELSE DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT NULL::INT AS id; END IF; END$$; DROP FUNCTION IF EXISTS pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command); DROP FUNCTION IF EXISTS pgl_ddl_deploy.get_altertable_subcmdtypes(pg_ddl_command); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ -- This handles potential duplicates with multiple subscriptions to same publisher db. IF EXISTS ( SELECT NEW.* INTERSECT SELECT * FROM pgl_ddl_deploy.queue) THEN RETURN NULL; END IF; IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, p_driver pgl_ddl_deploy.driver, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $function$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN IF pgl_ddl_deploy.is_subscriber(p_driver, p_set_name, p_provider_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $function$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT sc.id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, sc.driver, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||sc.id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_driver pgl_ddl_deploy.driver = '$BUILD$||sc.driver||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=pgl_ddl_deploy.provider_node_name(c_driver); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; RAISE DEBUG 'v_full_ddl: %', v_full_ddl; RAISE DEBUG 'c_set_config_id: %', c_set_config_id; RAISE DEBUG 'c_set_name: %', c_set_name; RAISE DEBUG 'c_driver: %', c_driver; RAISE DEBUG 'v_ddl_sql_sent: %', v_ddl_sql_sent; v_sql:=$INNER_BLOCK$ SELECT $BUILD$||CASE WHEN sc.driver = 'native' THEN 'pgl_ddl_deploy' WHEN sc.driver = 'pglogical' THEN 'pglogical' ELSE 'ERROR-EXCEPTION' END||$BUILD$.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||COALESCE(quote_literal(c_provider_name), 'NULL')||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$, p_driver := $INNER_BLOCK$||quote_literal(c_driver)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG 'v_sql: %', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = c_set_name AND rsr.relid = c.oid AND rsr.driver = c_driver) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.relid = c.objid AND c.object_type in('table','table column','table constraint') AND rsr.name = '$BUILD$||sc.set_name||$BUILD$' AND rsr.driver = '$BUILD$||sc.driver||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pgl_ddl_deploy.rep_set_wrapper() rs INNER JOIN pgl_ddl_deploy.set_configs sc ON sc.set_name = rs.name AND sc.driver = rs.driver ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN driver = 'pglogical' THEN '--no-op pglogical diver'::TEXT WHEN driver = 'native' THEN $BUILD$ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname = '$BUILD$||set_name||$BUILD$' AND schemaname = 'pgl_ddl_deploy' AND tablename = 'queue') THEN ALTER PUBLICATION $BUILD$||quote_ident(set_name)||$BUILD$ ADD TABLE pgl_ddl_deploy.queue; END IF; END$$; $BUILD$ END AS add_queue_table_to_replication, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pgl_ddl_deploy.add_table_to_replication( p_driver:=c_driver ,p_set_name:=c_set_name ,p_relation:=c.oid ,p_synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| b.add_queue_table_to_replication||$BUILD$ $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; -- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE IF EXISTS ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) FROM PUBLIC; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--2.1.sql000066400000000000000000003243171453171545300205740ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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 pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; --Just do this to avoid unneeded complexity with dependency_update -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN exclude_alter_table_subcommands TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_only_alter_table; SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs_id_seq', ''); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN ddl_only_replication BOOLEAN NOT NULL DEFAULT FALSE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table 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 pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id, NULL); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(NULL, p_set_name); END; $function$; CREATE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_tag(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_tag' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_type(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_type' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_repset_only_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$'::TEXT; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.common_exclude_alter_table_subcommands() RETURNS TEXT[] AS $BODY$ SELECT ARRAY[ 'ADD CONSTRAINT', 'ADD CONSTRAINT (and recurse)', '(re) ADD CONSTRAINT', 'ALTER CONSTRAINT', 'VALIDATE CONSTRAINT', 'VALIDATE CONSTRAINT (and recurse)', 'ADD (processed) CONSTRAINT', 'ADD CONSTRAINT (using index)', 'DROP CONSTRAINT', 'DROP CONSTRAINT (and recurse)', 'SET LOGGED', 'SET UNLOGGED', 'SET TABLESPACE', 'SET RELOPTIONS', 'RESET RELOPTIONS', 'REPLACE RELOPTIONS', 'ENABLE TRIGGER', 'ENABLE TRIGGER (always)', 'ENABLE TRIGGER (replica)', 'DISABLE TRIGGER', 'ENABLE TRIGGER (all)', 'DISABLE TRIGGER (all)', 'ENABLE TRIGGER (user)', 'DISABLE TRIGGER (user)', 'ENABLE RULE', 'ENABLE RULE (always)', 'ENABLE RULE (replica)', 'DISABLE RULE', 'SET OPTIONS']::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF NOT NEW.ddl_only_replication AND EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND NOT NEW.ddl_only_replication AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs, unless you are using the ddl_only_replication setting. $$, NEW.set_name; END IF; RETURN NEW; END; $function$ ; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_restricted_tags CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND pgl_ddl_deploy.standard_repset_only_tags() @> create_tags AND drop_tags IS NULL)); /* pgl_ddl_deploy--1.4--1.5.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_everything BOOLEAN NOT NULL DEFAULT FALSE; -- Now we have 3 configuration types ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_or_regex_inclusion; -- Only allow one of them to be chosen ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT single_configuration_type CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL) OR (include_everything AND NOT include_only_repset_tables AND include_schema_regex IS NULL)); ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT ddl_only_restrictions CHECK (NOT (ddl_only_replication AND include_only_repset_tables)); -- Need to adjust to after trigger and change function def DROP TRIGGER unique_tags ON pgl_ddl_deploy.set_configs; DROP FUNCTION pgl_ddl_deploy.unique_tags(); -- Support canceling or terminating blocking processes on subscriber CREATE TYPE pgl_ddl_deploy.signals AS ENUM ('cancel','terminate','cancel_then_terminate'); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN signal_blocking_subscriber_sessions pgl_ddl_deploy.signals; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN subscriber_lock_timeout INT; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_signal_blocker_config CHECK (NOT (lock_safe_deployment AND (signal_blocking_subscriber_sessions IS NOT NULL OR subscriber_lock_timeout IS NOT NULL)) AND NOT (subscriber_lock_timeout IS NOT NULL AND signal_blocking_subscriber_sessions IS NULL)); CREATE TABLE pgl_ddl_deploy.killed_blockers ( id SERIAL PRIMARY KEY, signal TEXT, successful BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN DEFAULT FALSE, reported_at TIMESTAMPTZ ); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_output TEXT; BEGIN WITH dupes AS ( SELECT set_name, CASE WHEN include_only_repset_tables THEN 'include_only_repset_tables' WHEN include_everything AND NOT ddl_only_replication THEN 'include_everything' WHEN include_schema_regex IS NOT NULL AND NOT ddl_only_replication THEN 'include_schema_regex' WHEN ddl_only_replication THEN CASE WHEN include_everything THEN 'ddl_only_include_everything' WHEN include_schema_regex IS NOT NULL THEN 'ddl_only_include_schema_regex' END END AS category, unnest(array_cat(create_tags, drop_tags)) AS command_tag FROM pgl_ddl_deploy.set_configs GROUP BY 1, 2, 3 HAVING COUNT(1) > 1) , aggregate_dupe_tags AS ( SELECT set_name, category, string_agg(command_tag, ', ' ORDER BY command_tag) AS command_tags FROM dupes GROUP BY 1, 2 ) SELECT string_agg(format('%s: %s: %s', set_name, category, command_tags), ', ') AS output INTO v_output FROM aggregate_dupe_tags; IF v_output IS NOT NULL THEN RAISE EXCEPTION '%', format('You have overlapping configuration types and command tags which is not permitted: %s', v_output); END IF; RETURN NULL; END; $function$ ; CREATE TRIGGER unique_tags AFTER INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND n.nspname = p_nspname AND c.relname = p_relname AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(name, regclass, boolean, text[], text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_set_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, p_message); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $function$ ; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; /* pgl_ddl_deploy--1.5--1.6.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.current_query() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgl_ddl_deploy_current_query' LANGUAGE C VOLATILE STRICT; -- Drop UPDATE event for this trigger, which leads to unexpected behavior DROP TRIGGER set_tag_defaults ON pgl_ddl_deploy.set_configs; CREATE TRIGGER set_tag_defaults BEFORE INSERT ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT DISTINCT ON (l.pid) p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid /*** We need to check if this is an inheritance parent, because even a share lock on a child will prevent DDL on parent ***/ LEFT JOIN pg_inherits pi ON pi.inhrelid = c.oid LEFT JOIN pg_class ipc on ipc.oid = pi.inhparent LEFT JOIN pg_namespace ipn on ipn.oid = ipc.relnamespace -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND ((n.nspname = p_nspname AND c.relname = p_relname) OR (ipn.nspname = p_nspname AND ipc.relname = p_relname)) AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY l.pid, a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, REPLACE(p_message,'%','%%')); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT ,"CREATE RULE" ,"CREATE TRIGGER" ,"ALTER TRIGGER"}'::TEXT[]; $function$ ; /* pgl_ddl_deploy--1.6--1.7.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; /* pgl_ddl_deploy--1.7--2.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_views WHERE schemaname = 'pgl_ddl_deploy' AND viewname = 'event_trigger_schema') THEN DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; ELSE DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT NULL::INT AS id; END IF; END$$; CREATE TYPE pgl_ddl_deploy.driver AS ENUM ('pglogical', 'native'); -- Not possible that any existing config would be native, so: ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN driver pgl_ddl_deploy.driver NOT NULL DEFAULT 'pglogical'; DROP FUNCTION IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper(); DROP FUNCTION IF EXISTS pgl_ddl_deploy.deployment_check_count(integer, text, text); DROP FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN ); CREATE TABLE pgl_ddl_deploy.queue( queued_at timestamp with time zone not null, role name not null, pubnames text[], message_type "char" not null, message text not null ); COMMENT ON TABLE pgl_ddl_deploy.queue IS 'Modeled on the pglogical.queue table for native logical replication ddl'; ALTER TABLE pgl_ddl_deploy.queue REPLICA IDENTITY FULL; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; -- NOTE - this duplicates execute_queued_ddl.sql function file but is executed here for the upgrade/build path CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; CREATE TRIGGER execute_queued_ddl BEFORE INSERT ON pgl_ddl_deploy.queue FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.execute_queued_ddl(); -- This must only fire on the replica ALTER TABLE pgl_ddl_deploy.queue ENABLE REPLICA TRIGGER execute_queued_ddl; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.replicate_ddl_command(command text, pubnames text[]) RETURNS BOOLEAN LANGUAGE plpgsql AS $function$ -- Modeled after pglogical's replicate_ddl_command but in support of native logical replication BEGIN -- NOTE: pglogical uses clock_timestamp() to log queued_at times and we do the same here INSERT INTO pgl_ddl_deploy.queue (queued_at, role, pubnames, message_type, message) VALUES (clock_timestamp(), current_role, pubnames, pgl_ddl_deploy.queue_ddl_message_type(), command); RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_table_to_replication(p_driver pgl_ddl_deploy.driver, p_set_name name, p_relation regclass, p_synchronize_data boolean DEFAULT false) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_schema NAME; v_table NAME; v_result BOOLEAN = false; BEGIN IF p_driver = 'pglogical' THEN SELECT pglogical.replication_set_add_table( set_name:=p_set_name ,relation:=p_relation ,synchronize_data:=p_synchronize_data ) INTO v_result; ELSEIF p_driver = 'native' THEN SELECT nspname, relname INTO v_schema, v_table FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.oid = p_relation::OID; EXECUTE 'ALTER PUBLICATION '||quote_ident(p_set_name)||' ADD TABLE '||quote_ident(v_schema)||'.'||quote_ident(v_table)||';'; -- We use true to synchronize data here, not taking the value from p_synchronize_data. This is because of the different way -- that native logical works, and that changes are not queued from the time of the table being added to replication. Thus, we -- by default WILL use COPY_DATA = true -- This needs to be in a DO block currently because of how the DDL is processed on the subscriber. PERFORM pgl_ddl_deploy.replicate_ddl_command($$DO $AUTO_REPLICATE_BLOCK$ BEGIN PERFORM pgl_ddl_deploy.notify_subscription_refresh('$$||p_set_name||$$', true); END$AUTO_REPLICATE_BLOCK$;$$, array[p_set_name]); v_result = true; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; RETURN v_result; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.notify_subscription_refresh(p_set_name name, p_copy_data boolean DEFAULT TRUE) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pg_subscription WHERE subpublications && array[p_set_name::text]) THEN RAISE EXCEPTION 'No subscription to publication % exists', p_set_name; END IF; FOR v_rec IN SELECT unnest(subpublications) AS pubname, subname FROM pg_subscription WHERE subpublications && array[p_set_name::text] LOOP v_sql = $$ALTER SUBSCRIPTION $$||quote_ident(v_rec.subname)||$$ REFRESH PUBLICATION WITH ( COPY_DATA = '$$||p_copy_data||$$');$$; RAISE LOG 'pgl_ddl_deploy executing: %', v_sql; EXECUTE v_sql; END LOOP; RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (id OID, relid REGCLASS, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id); ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id); ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; END IF; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_wrapper() RETURNS TABLE (id OID, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs; ELSE RAISE EXCEPTION 'pglogical required for version prior to Postgres 10'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs UNION ALL SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSE RAISE EXCEPTION 'Unexpected exception'; END IF; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text, p_driver pgl_ddl_deploy.driver) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = p_set_name AND rsr.relid = c.oid AND rsr.driver = p_driver); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = '$SQL$||p_set_name||$SQL$' AND rsr.relid = c.oid AND rsr.driver = (SELECT driver FROM pgl_ddl_deploy.set_configs WHERE set_name = '$SQL$||p_set_name||$SQL$')); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; v_driver pgl_ddl_deploy.driver; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication, driver INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication, v_driver FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex, v_driver); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.is_subscriber(p_driver pgl_ddl_deploy.driver, p_name TEXT[], p_provider_name NAME = NULL) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN IF p_driver = 'pglogical' THEN RETURN EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_name); ELSEIF p_driver = 'native' THEN RETURN EXISTS (SELECT 1 FROM pg_subscription s WHERE subpublications && p_name); ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, p_driver pgl_ddl_deploy.driver, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN IF pgl_ddl_deploy.is_subscriber(p_driver, p_set_name, p_provider_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.queue_ddl_message_type() RETURNS "char" LANGUAGE sql IMMUTABLE AS $function$ SELECT 'Q'::"char"; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.provider_node_name(p_driver pgl_ddl_deploy.driver) RETURNS NAME LANGUAGE plpgsql AS $function$ DECLARE v_node_name NAME; BEGIN IF p_driver = 'pglogical' THEN SELECT n.node_name INTO v_node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id); RETURN v_node_name; ELSEIF p_driver = 'native' THEN RETURN NULL::NAME; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT sc.id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, sc.driver, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||sc.id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_driver pgl_ddl_deploy.driver = '$BUILD$||sc.driver||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=pgl_ddl_deploy.provider_node_name(c_driver); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; RAISE DEBUG 'v_full_ddl: %', v_full_ddl; RAISE DEBUG 'c_set_config_id: %', c_set_config_id; RAISE DEBUG 'c_set_name: %', c_set_name; RAISE DEBUG 'c_driver: %', c_driver; RAISE DEBUG 'v_ddl_sql_sent: %', v_ddl_sql_sent; v_sql:=$INNER_BLOCK$ SELECT $BUILD$||CASE WHEN sc.driver = 'native' THEN 'pgl_ddl_deploy' WHEN sc.driver = 'pglogical' THEN 'pglogical' ELSE 'ERROR-EXCEPTION' END||$BUILD$.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||COALESCE(quote_literal(c_provider_name), 'NULL')||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$, p_driver := $INNER_BLOCK$||quote_literal(c_driver)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG 'v_sql: %', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = c_set_name AND rsr.relid = c.oid AND rsr.driver = c_driver) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.relid = c.objid AND c.object_type in('table','table column','table constraint') AND rsr.name = '$BUILD$||sc.set_name||$BUILD$' AND rsr.driver = '$BUILD$||sc.driver||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pgl_ddl_deploy.rep_set_wrapper() rs INNER JOIN pgl_ddl_deploy.set_configs sc ON sc.set_name = rs.name AND sc.driver = rs.driver ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN driver = 'pglogical' THEN '--no-op pglogical diver'::TEXT WHEN driver = 'native' THEN $BUILD$ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname = '$BUILD$||set_name||$BUILD$' AND schemaname = 'pgl_ddl_deploy' AND tablename = 'queue') THEN ALTER PUBLICATION $BUILD$||quote_ident(set_name)||$BUILD$ ADD TABLE pgl_ddl_deploy.queue; END IF; END$$; $BUILD$ END AS add_queue_table_to_replication, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pgl_ddl_deploy.add_table_to_replication( p_driver:=c_driver ,p_set_name:=c_set_name ,p_relation:=c.oid ,p_synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| b.add_queue_table_to_replication||$BUILD$ $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||';'; EXECUTE v_sql; IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; END IF; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; -- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE IF EXISTS ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) FROM PUBLIC; /* pgl_ddl_deploy--2.0--2.1.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ -- This handles potential duplicates with multiple subscriptions to same publisher db. IF EXISTS ( SELECT NEW.* INTERSECT SELECT * FROM pgl_ddl_deploy.queue) THEN RETURN NULL; END IF; IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy--2.2.sql000066400000000000000000004343561453171545300206020ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.sql_command_tags(p_sql TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'sql_command_tags' LANGUAGE C VOLATILE STRICT; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'ADD'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.drop_ext_object (p_type text , p_full_obj_name text) RETURNS VOID AS $BODY$ BEGIN PERFORM pgl_ddl_deploy.toggle_ext_object(p_type, p_full_obj_name, 'DROP'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.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']; c_valid_toggles TEXT[] = ARRAY['ADD','DROP']; BEGIN IF NOT (SELECT ARRAY[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[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 pgl_ddl_deploy '||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 pgl_ddl_deploy.exclude_regex() RETURNS TEXT AS $BODY$ SELECT '^(pg_catalog|information_schema|pg_temp|pg_toast|pgl_ddl_deploy|pglogical).*'::TEXT; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, SELECT, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE TABLE pgl_ddl_deploy.set_configs ( set_name NAME PRIMARY KEY, include_schema_regex TEXT NOT NULL, lock_safe_deployment BOOLEAN DEFAULT FALSE NOT NULL, allow_multi_statements BOOLEAN DEFAULT TRUE NOT NULL, CONSTRAINT valid_regex CHECK (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END) ); SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs', ''); CREATE TABLE pgl_ddl_deploy.events ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, ddl_sql_sent TEXT, txid BIGINT ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.events (set_name, pid, txid, md5(ddl_sql_raw)); CREATE TABLE pgl_ddl_deploy.exceptions ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT, err_msg TEXT, err_state TEXT); CREATE TABLE pgl_ddl_deploy.unhandled ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql_raw TEXT, command_tag TEXT, reason TEXT, txid BIGINT, CONSTRAINT valid_reason CHECK (reason IN('mixed_objects','rejected_command_tags','rejected_multi_statement','unsupported_command')) ); CREATE UNIQUE INDEX ON pgl_ddl_deploy.unhandled (set_name, pid, txid, md5(ddl_sql_raw)); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; CREATE TABLE pgl_ddl_deploy.subscriber_logs ( id SERIAL PRIMARY KEY, set_name NAME, provider_pid INT, subscriber_pid INT, executed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, ddl_sql TEXT); CREATE TABLE pgl_ddl_deploy.commands ( id SERIAL PRIMARY KEY, set_name NAME, pid INT, txid BIGINT, classid Oid, objid Oid, objsubid integer, command_tag text, object_type text, schema_name text, object_identity text, in_extension bool); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.lock_safe_executor(p_sql TEXT) RETURNS VOID AS $BODY$ BEGIN SET lock_timeout TO '10ms'; LOOP BEGIN EXECUTE p_sql; EXIT; EXCEPTION WHEN lock_not_available THEN RAISE WARNING 'Could not obtain immediate lock for SQL %, retrying', p_sql; PERFORM pg_sleep(3); WHEN OTHERS THEN RAISE; END; END LOOP; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_name) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_name, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO PUBLIC; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN GRANT USAGE ON SCHEMA pglogical TO PUBLIC; REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pglogical FROM PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'dependency_check_trigger' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.dependency_check_trigger() TO PUBLIC; END IF; IF EXISTS (SELECT 1 FROM pg_proc p INNER JOIN pg_namespace n ON n.oid = p.pronamespace WHERE proname = 'truncate_trigger_add' AND nspname = 'pglogical') THEN GRANT EXECUTE ON FUNCTION pglogical.truncate_trigger_add() TO PUBLIC; END IF; END$$; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) FROM PUBLIC; -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /**** We first need to drop existing event triggers and functions, because the naming convention is changing */ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs AS WITH old_named_objects AS (SELECT set_name, 'pgl_ddl_deploy.auto_replicate_ddl_'||set_name||'()' AS auto_replication_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_drop_'||set_name||'()' AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_replicate_ddl_unsupported_'||set_name||'()' AS auto_replication_unsupported_function_name, 'auto_replicate_ddl_'||set_name AS auto_replication_trigger_name, 'auto_replicate_ddl_drop_'||set_name AS auto_replication_drop_trigger_name, 'auto_replicate_ddl_unsupported_'||set_name AS auto_replication_unsupported_trigger_name FROM pgl_ddl_deploy.set_configs) SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_drop_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'EVENT TRIGGER' AS obj_type, auto_replication_unsupported_trigger_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_drop_function_name AS obj_name FROM old_named_objects UNION ALL SELECT set_name, 'FUNCTION' AS obj_type, auto_replication_unsupported_function_name AS obj_name FROM old_named_objects ; SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DO $BUILD$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'EVENT TRIGGER' LOOP v_sql = $$DROP EVENT TRIGGER IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Event trigger % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT * FROM tmp_objs WHERE obj_type = 'FUNCTION' LOOP v_sql = $$DROP FUNCTION IF EXISTS $$||v_rec.obj_name||$$;$$; EXECUTE v_sql; RAISE WARNING 'Function % dropped', v_rec.obj_name; END LOOP; FOR v_rec IN SELECT DISTINCT set_name FROM tmp_objs LOOP RAISE WARNING $$Objects changed - you must manually re-deploy using pgl_ddl_deploy.deploy('%')$$, v_rec.set_name; END LOOP; END $BUILD$; --If you don't do this, it will be part of the extension! DROP TABLE tmp_objs; CREATE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.standard_drop_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "DROP SCHEMA" ,"DROP TABLE" ,"DROP FUNCTION" ,"DROP TYPE" ,"DROP VIEW" ,"DROP SEQUENCE" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE FUNCTION pgl_ddl_deploy.unsupported_tags() RETURNS TEXT[] AS $BODY$ SELECT '{ "CREATE TABLE AS" ,"SELECT INTO" }'::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN id SERIAL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT set_configs_pkey; ALTER TABLE pgl_ddl_deploy.set_configs ADD PRIMARY KEY (id); ALTER TABLE pgl_ddl_deploy.commands ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.events ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN set_config_id INT REFERENCES pgl_ddl_deploy.set_configs (id); ALTER EXTENSION pgl_ddl_deploy DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); DROP FUNCTION pgl_ddl_deploy.log_unhandled (TEXT, INT, TEXT, TEXT, TEXT, BIGINT); CREATE FUNCTION pgl_ddl_deploy.log_unhandled (p_set_config_id INT, p_set_name TEXT, p_pid INT, p_ddl_sql_raw TEXT, p_command_tag TEXT, p_reason TEXT, p_txid BIGINT) RETURNS VOID AS $BODY$ DECLARE c_unhandled_msg TEXT = 'Unhandled deployment logged in pgl_ddl_deploy.unhandled'; BEGIN INSERT INTO pgl_ddl_deploy.unhandled (set_config_id, set_name, pid, executed_at, ddl_sql_raw, command_tag, reason, txid) VALUES (p_set_config_id, p_set_name, p_pid, current_timestamp, p_ddl_sql_raw, p_command_tag, p_reason, p_txid); RAISE WARNING '%', c_unhandled_msg; END; $BODY$ LANGUAGE plpgsql; --Allow specific tables or include regex ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN include_schema_regex DROP NOT NULL; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT valid_regex; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_regex CHECK (include_schema_regex IS NULL OR (CASE WHEN regexp_replace('',include_schema_regex,'') = '' THEN TRUE ELSE FALSE END)); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_only_repset_tables BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_or_regex_inclusion CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL)); --Customize command tags ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN create_tags TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN drop_tags TEXT[]; UPDATE pgl_ddl_deploy.set_configs SET create_tags = pgl_ddl_deploy.standard_create_tags(), drop_tags = pgl_ddl_deploy.standard_drop_tags(); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN blacklisted_tags TEXT[] DEFAULT pgl_ddl_deploy.blacklisted_tags(); --Allow failures ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN queue_subscriber_failures BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_only_alter_table CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND create_tags = '{"ALTER TABLE"}' AND drop_tags IS NULL)); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS TRIGGER AS $BODY$ BEGIN IF EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs. $$, NEW.set_name; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER unique_tags BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.set_tag_defaults() RETURNS TRIGGER AS $BODY$ BEGIN IF NEW.create_tags IS NULL THEN NEW.create_tags = CASE WHEN NEW.include_only_repset_tables THEN '{"ALTER TABLE"}' ELSE pgl_ddl_deploy.standard_create_tags() END; END IF; IF NEW.drop_tags IS NULL THEN NEW.drop_tags = CASE WHEN NEW.include_only_repset_tables THEN NULL ELSE pgl_ddl_deploy.standard_drop_tags() END; END IF; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_tag_defaults BEFORE INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ADD COLUMN full_ddl_sql TEXT, ADD COLUMN origin_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN next_subscriber_log_id INT NULL REFERENCES pgl_ddl_deploy.subscriber_logs(id), ADD COLUMN provider_node_name TEXT, ADD COLUMN provider_set_config_id INT, ADD COLUMN executed_as_role TEXT DEFAULT current_role, ADD COLUMN retrying BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN succeeded BOOLEAN NULL, ADD COLUMN error_message TEXT; CREATE FUNCTION pgl_ddl_deploy.set_origin_subscriber_log_id() RETURNS TRIGGER AS $BODY$ BEGIN NEW.origin_subscriber_log_id = NEW.id; RETURN NEW; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER set_origin_subscriber_log_id BEFORE INSERT ON pgl_ddl_deploy.subscriber_logs FOR EACH ROW WHEN (NEW.origin_subscriber_log_id IS NULL) EXECUTE PROCEDURE pgl_ddl_deploy.set_origin_subscriber_log_id(); ALTER TABLE pgl_ddl_deploy.subscriber_logs ENABLE REPLICA TRIGGER set_origin_subscriber_log_id; CREATE UNIQUE INDEX unique_untried ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE NOT succeeded AND next_subscriber_log_id IS NULL AND NOT retrying; CREATE UNIQUE INDEX unique_retrying ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE retrying; CREATE UNIQUE INDEX unique_succeeded ON pgl_ddl_deploy.subscriber_logs (origin_subscriber_log_id) WHERE succeeded; CREATE FUNCTION pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id INT, p_error_message TEXT) RETURNS VOID AS $BODY$ DECLARE v_new_subscriber_log_id INT; BEGIN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, error_message, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, p_error_message, FALSE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING id INTO v_new_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = v_new_subscriber_log_id WHERE id = p_subscriber_log_id; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_subscriber_log(p_subscriber_log_id INT) RETURNS BOOLEAN AS $BODY$ DECLARE v_sql TEXT; v_role TEXT; v_return BOOLEAN; BEGIN IF (SELECT retrying FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id) = TRUE THEN RAISE WARNING 'This subscriber_log_id is already executing. No action will be taken.'; RETURN FALSE; END IF; SELECT full_ddl_sql, executed_as_role INTO v_sql, v_role FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = TRUE WHERE id = p_subscriber_log_id; BEGIN /** This needs to be a DO block because currently,the final SQL sent to subscriber is always within a DO block */ v_sql = $$ DO $RETRY$ BEGIN SET ROLE $$||quote_ident(v_role)||$$; $$||v_sql||$$ END$RETRY$; $$; EXECUTE v_sql; RESET ROLE; WITH success AS ( INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, subscriber_pid, ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, succeeded) SELECT set_name, provider_pid, pg_backend_pid(), ddl_sql, full_ddl_sql, origin_subscriber_log_id, provider_node_name, provider_set_config_id, executed_as_role, TRUE FROM pgl_ddl_deploy.subscriber_logs WHERE id = p_subscriber_log_id RETURNING * ) UPDATE pgl_ddl_deploy.subscriber_logs SET next_subscriber_log_id = (SELECT id FROM success) WHERE id = p_subscriber_log_id; v_return = TRUE; EXCEPTION WHEN OTHERS THEN PERFORM pgl_ddl_deploy.fail_queued_attempt(p_subscriber_log_id, SQLERRM); v_return = FALSE; END; UPDATE pgl_ddl_deploy.subscriber_logs SET retrying = FALSE WHERE id = p_subscriber_log_id; RETURN v_return; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.retry_all_subscriber_logs() RETURNS BOOLEAN[] AS $BODY$ DECLARE v_rec RECORD; v_result BOOLEAN; v_results BOOLEAN[]; BEGIN FOR v_rec IN SELECT rq.id FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC LOOP SELECT pgl_ddl_deploy.retry_subscriber_log(v_rec.id) INTO v_result; v_results = array_append(v_results, v_result); IF NOT v_result THEN RETURN v_results; END IF; END LOOP; RETURN v_results; END; $BODY$ LANGUAGE plpgsql; --Allow a mechanism to mark unhandled and exceptions as resolved for monitoring purposes ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.unhandled ADD COLUMN resolved_notes TEXT NULL; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE pgl_ddl_deploy.exceptions ADD COLUMN resolved_notes TEXT NULL; CREATE FUNCTION pgl_ddl_deploy.resolve_unhandled(p_unhandled_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.unhandled SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_unhandled_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION pgl_ddl_deploy.resolve_exception(p_exception_id INT, p_notes TEXT = NULL) RETURNS BOOLEAN AS $BODY$ DECLARE v_row_count INT; BEGIN UPDATE pgl_ddl_deploy.exceptions SET resolved = TRUE, resolved_notes = p_notes WHERE id = p_exception_id; GET DIAGNOSTICS v_row_count = ROW_COUNT; RETURN (v_row_count > 0); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events SELECT id, include_schema_regex INTO c_set_config_id, c_include_schema_regex FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, p_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; c_set_name TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id) THEN RETURN FALSE; END IF; --This check only applicable to non-include_only_repset_tables and sets using CREATE TABLE events --We re-assign set_config_id because we want to know if no records are found, leading to NULL SELECT id, include_schema_regex, set_name INTO c_set_config_id, c_include_schema_regex, c_set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id AND NOT include_only_repset_tables AND create_tags && '{"CREATE TABLE"}'::TEXT[]; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'deploy_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.enable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_deployable BOOLEAN; v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.deployment_check(p_set_config_id) INTO v_deployable; IF v_deployable THEN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'enable_sql') INTO v_result; RETURN v_result; ELSE RETURN v_deployable; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.disable(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ DECLARE v_result BOOLEAN; BEGIN SELECT pgl_ddl_deploy.schema_execute(p_set_config_id, 'disable_sql') INTO v_result; RETURN v_result; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_name text, p_field_name text) RETURNS BOOLEAN AS $BODY$ /**** This function will deploy SQL for all set_configs with given set_name, since this is now allowed. The version of this function with (int, text) uses a single set_config_id to deploy */ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN FOR v_rec IN SELECT id FROM pgl_ddl_deploy.set_configs WHERE set_name = p_set_name LOOP v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||v_rec.id||$$ AND set_name = '$$||p_set_name||$$');$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', v_rec.id, p_set_name; RETURN FALSE; ELSE EXECUTE v_out_sql; END IF; END LOOP; RETURN TRUE; END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.schema_execute(p_set_config_id int, p_field_name text) RETURNS BOOLEAN AS $BODY$ DECLARE v_rec RECORD; v_in_sql TEXT; v_out_sql TEXT; BEGIN v_in_sql = $$(SELECT $$||p_field_name||$$ FROM pgl_ddl_deploy.event_trigger_schema WHERE id = $$||p_set_config_id||$$);$$; EXECUTE v_in_sql INTO v_out_sql; IF v_out_sql IS NULL THEN RAISE WARNING 'Failed execution for id % set %', p_set_config_id, (SELECT set_name FROM pgl_ddl_deploy.set_configs WHERE id = p_set_config_id); RETURN FALSE; ELSE EXECUTE v_out_sql; RETURN TRUE; END IF; END; $BODY$ LANGUAGE plpgsql; --Just do this to avoid unneeded complexity with dependency_update -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_config_id int) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_config_id, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.undeploy(p_set_name text) RETURNS BOOLEAN AS $BODY$ BEGIN RETURN pgl_ddl_deploy.schema_execute(p_set_name, 'undeploy_sql'); END; $BODY$ LANGUAGE plpgsql; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN exclude_alter_table_subcommands TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_only_alter_table; SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs_id_seq', ''); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN ddl_only_replication BOOLEAN NOT NULL DEFAULT FALSE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (set_id OID, set_reloid REGCLASS) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table 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 pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_config_id integer) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id, NULL); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check(p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN RETURN pgl_ddl_deploy.deployment_check_wrapper(NULL, p_set_name); END; $function$; CREATE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_tag(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_tag' LANGUAGE C; CREATE FUNCTION pgl_ddl_deploy.get_command_type(pg_ddl_command) RETURNS text IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_command_type' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_repset_only_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT}'::TEXT[]; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.exclude_regex() RETURNS text LANGUAGE sql IMMUTABLE AS $function$ SELECT '^(pg_catalog|information_schema|pg_temp.*|pg_toast.*|pgl_ddl_deploy|pglogical|pglogical_ticker|repack)$'::TEXT; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.common_exclude_alter_table_subcommands() RETURNS TEXT[] AS $BODY$ SELECT ARRAY[ 'ADD CONSTRAINT', 'ADD CONSTRAINT (and recurse)', '(re) ADD CONSTRAINT', 'ALTER CONSTRAINT', 'VALIDATE CONSTRAINT', 'VALIDATE CONSTRAINT (and recurse)', 'ADD (processed) CONSTRAINT', 'ADD CONSTRAINT (using index)', 'DROP CONSTRAINT', 'DROP CONSTRAINT (and recurse)', 'SET LOGGED', 'SET UNLOGGED', 'SET TABLESPACE', 'SET RELOPTIONS', 'RESET RELOPTIONS', 'REPLACE RELOPTIONS', 'ENABLE TRIGGER', 'ENABLE TRIGGER (always)', 'ENABLE TRIGGER (replica)', 'DISABLE TRIGGER', 'ENABLE TRIGGER (all)', 'DISABLE TRIGGER (all)', 'ENABLE TRIGGER (user)', 'DISABLE TRIGGER (user)', 'ENABLE RULE', 'ENABLE RULE (always)', 'ENABLE RULE (replica)', 'DISABLE RULE', 'SET OPTIONS']::TEXT[]; $BODY$ LANGUAGE SQL IMMUTABLE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN IF NOT NEW.ddl_only_replication AND EXISTS ( SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE id <> NEW.id AND set_name = NEW.set_name AND NOT NEW.ddl_only_replication AND (create_tags && NEW.create_tags OR drop_tags && NEW.drop_tags)) THEN RAISE EXCEPTION $$Another set_config already exists for '%' with overlapping create_tags or drop_tags. Command tags must only appear once per set_name even if using multiple set_configs, unless you are using the ddl_only_replication setting. $$, NEW.set_name; END IF; RETURN NEW; END; $function$ ; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_restricted_tags CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND pgl_ddl_deploy.standard_repset_only_tags() @> create_tags AND drop_tags IS NULL)); /* pgl_ddl_deploy--1.4--1.5.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_everything BOOLEAN NOT NULL DEFAULT FALSE; -- Now we have 3 configuration types ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_or_regex_inclusion; -- Only allow one of them to be chosen ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT single_configuration_type CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL) OR (include_everything AND NOT include_only_repset_tables AND include_schema_regex IS NULL)); ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT ddl_only_restrictions CHECK (NOT (ddl_only_replication AND include_only_repset_tables)); -- Need to adjust to after trigger and change function def DROP TRIGGER unique_tags ON pgl_ddl_deploy.set_configs; DROP FUNCTION pgl_ddl_deploy.unique_tags(); -- Support canceling or terminating blocking processes on subscriber CREATE TYPE pgl_ddl_deploy.signals AS ENUM ('cancel','terminate','cancel_then_terminate'); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN signal_blocking_subscriber_sessions pgl_ddl_deploy.signals; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN subscriber_lock_timeout INT; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_signal_blocker_config CHECK (NOT (lock_safe_deployment AND (signal_blocking_subscriber_sessions IS NOT NULL OR subscriber_lock_timeout IS NOT NULL)) AND NOT (subscriber_lock_timeout IS NOT NULL AND signal_blocking_subscriber_sessions IS NULL)); CREATE TABLE pgl_ddl_deploy.killed_blockers ( id SERIAL PRIMARY KEY, signal TEXT, successful BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN DEFAULT FALSE, reported_at TIMESTAMPTZ ); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.unique_tags() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE v_output TEXT; BEGIN WITH dupes AS ( SELECT set_name, CASE WHEN include_only_repset_tables THEN 'include_only_repset_tables' WHEN include_everything AND NOT ddl_only_replication THEN 'include_everything' WHEN include_schema_regex IS NOT NULL AND NOT ddl_only_replication THEN 'include_schema_regex' WHEN ddl_only_replication THEN CASE WHEN include_everything THEN 'ddl_only_include_everything' WHEN include_schema_regex IS NOT NULL THEN 'ddl_only_include_schema_regex' END END AS category, unnest(array_cat(create_tags, drop_tags)) AS command_tag FROM pgl_ddl_deploy.set_configs GROUP BY 1, 2, 3 HAVING COUNT(1) > 1) , aggregate_dupe_tags AS ( SELECT set_name, category, string_agg(command_tag, ', ' ORDER BY command_tag) AS command_tags FROM dupes GROUP BY 1, 2 ) SELECT string_agg(format('%s: %s: %s', set_name, category, command_tags), ', ') AS output INTO v_output FROM aggregate_dupe_tags; IF v_output IS NOT NULL THEN RAISE EXCEPTION '%', format('You have overlapping configuration types and command tags which is not permitted: %s', v_output); END IF; RETURN NULL; END; $function$ ; CREATE TRIGGER unique_tags AFTER INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND n.nspname = p_nspname AND c.relname = p_relname AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(name, regclass, boolean, text[], text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN --Only run on subscriber with this replication set, and matching provider node name IF EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_set_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, p_message); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.blacklisted_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ INSERT, UPDATE, DELETE, TRUNCATE, ROLLBACK, "CREATE EXTENSION", "ALTER EXTENSION", "DROP EXTENSION"}'::TEXT[]; $function$ ; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; /* pgl_ddl_deploy--1.5--1.6.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE FUNCTION pgl_ddl_deploy.current_query() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgl_ddl_deploy_current_query' LANGUAGE C VOLATILE STRICT; -- Drop UPDATE event for this trigger, which leads to unexpected behavior DROP TRIGGER set_tag_defaults ON pgl_ddl_deploy.set_configs; CREATE TRIGGER set_tag_defaults BEFORE INSERT ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.kill_blockers (p_signal pgl_ddl_deploy.signals, p_nspname NAME, p_relname NAME) RETURNS TABLE ( signal pgl_ddl_deploy.signals, successful BOOLEAN, raised_message BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN ) AS $BODY$ /**** This function is only called on the subscriber on which we are applying DDL, when it is blocked and hits the configured lock_timeout. It is called by the function pgl_ddl_deploy.subscriber_command() only if it hits lock_timeout and it is configured to send a signal to blocking queries. It has three main features: 1. Signal blocking sessions with either cancel or terminate. 2. Raise a WARNING message to server logs in case of a kill attempt 3. Return the recordset with details of killed queries for auditing purposes. ****/ BEGIN RETURN QUERY SELECT DISTINCT ON (l.pid) p_signal AS signal, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pg_cancel_backend(l.pid) WHEN p_signal = 'terminate' THEN pg_terminate_backend(l.pid) END AS successful, CASE WHEN p_signal IS NULL THEN FALSE WHEN p_signal = 'cancel' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting cancel of blocking pid %s, query: %s', l.pid, a.query)) WHEN p_signal = 'terminate' THEN pgl_ddl_deploy.raise_message('WARNING', format('Attempting termination of blocking pid %s, query: %s', l.pid, a.query)) END AS raised_message, l.pid, now() AS executed_at, a.usename, a.client_addr, a.xact_start, a.state_change, a.state, a.query, FALSE AS reported FROM pg_locks l INNER JOIN pg_class c on l.relation = c.oid INNER JOIN pg_namespace n on c.relnamespace = n.oid INNER JOIN pg_stat_activity a on l.pid = a.pid /*** We need to check if this is an inheritance parent, because even a share lock on a child will prevent DDL on parent ***/ LEFT JOIN pg_inherits pi ON pi.inhrelid = c.oid LEFT JOIN pg_class ipc on ipc.oid = pi.inhparent LEFT JOIN pg_namespace ipn on ipn.oid = ipc.relnamespace -- We do not exclude either postgres user or pglogical processes, because we even want to cancel autovac blocks. -- It should not be possible to contend with pglogical write processes (at least as of pglogical 2.2), because -- these run single-threaded using the same process that is doing the DDL and already holds any lock it needs -- on the target table. WHERE NOT a.pid = pg_backend_pid() -- both nspname and relname will be an empty string, thus a no-op, if for some reason one or the other -- is not found on the provider side in pg_event_trigger_ddl_commands(). This is a safety mechanism! AND ((n.nspname = p_nspname AND c.relname = p_relname) OR (ipn.nspname = p_nspname AND ipc.relname = p_relname)) AND a.datname = current_database() AND c.relkind = 'r' AND l.locktype = 'relation' ORDER BY l.pid, a.state_change DESC; END; $BODY$ SECURITY DEFINER LANGUAGE plpgsql VOLATILE; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, NAME, NAME) FROM PUBLIC; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.raise_message (p_log_level TEXT, p_message TEXT) RETURNS BOOLEAN AS $BODY$ BEGIN EXECUTE format($$ DO $block$ BEGIN RAISE %s $pgl_ddl_deploy_msg$%s$pgl_ddl_deploy_msg$; END$block$; $$, p_log_level, REPLACE(p_message,'%','%%')); RETURN TRUE; END; $BODY$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.standard_create_tags() RETURNS text[] LANGUAGE sql IMMUTABLE AS $function$ SELECT '{ "ALTER TABLE" ,"CREATE SEQUENCE" ,"ALTER SEQUENCE" ,"CREATE SCHEMA" ,"CREATE TABLE" ,"CREATE FUNCTION" ,"ALTER FUNCTION" ,"CREATE TYPE" ,"ALTER TYPE" ,"CREATE VIEW" ,"ALTER VIEW" ,COMMENT ,"CREATE RULE" ,"CREATE TRIGGER" ,"ALTER TRIGGER"}'::TEXT[]; $function$ ; /* pgl_ddl_deploy--1.6--1.7.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; RETURN true; END LOOP; RETURN false; END; $function$ ; /* pgl_ddl_deploy--1.7--2.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_views WHERE schemaname = 'pgl_ddl_deploy' AND viewname = 'event_trigger_schema') THEN DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; ELSE DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT NULL::INT AS id; END IF; END$$; CREATE TYPE pgl_ddl_deploy.driver AS ENUM ('pglogical', 'native'); -- Not possible that any existing config would be native, so: ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN driver pgl_ddl_deploy.driver NOT NULL DEFAULT 'pglogical'; DROP FUNCTION IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper(); DROP FUNCTION IF EXISTS pgl_ddl_deploy.deployment_check_count(integer, text, text); DROP FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN ); CREATE TABLE pgl_ddl_deploy.queue( queued_at timestamp with time zone not null, role name not null, pubnames text[], message_type "char" not null, message text not null ); COMMENT ON TABLE pgl_ddl_deploy.queue IS 'Modeled on the pglogical.queue table for native logical replication ddl'; ALTER TABLE pgl_ddl_deploy.queue REPLICA IDENTITY FULL; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; -- NOTE - this duplicates execute_queued_ddl.sql function file but is executed here for the upgrade/build path CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; CREATE TRIGGER execute_queued_ddl BEFORE INSERT ON pgl_ddl_deploy.queue FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.execute_queued_ddl(); -- This must only fire on the replica ALTER TABLE pgl_ddl_deploy.queue ENABLE REPLICA TRIGGER execute_queued_ddl; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.replicate_ddl_command(command text, pubnames text[]) RETURNS BOOLEAN LANGUAGE plpgsql AS $function$ -- Modeled after pglogical's replicate_ddl_command but in support of native logical replication BEGIN -- NOTE: pglogical uses clock_timestamp() to log queued_at times and we do the same here INSERT INTO pgl_ddl_deploy.queue (queued_at, role, pubnames, message_type, message) VALUES (clock_timestamp(), current_role, pubnames, pgl_ddl_deploy.queue_ddl_message_type(), command); RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_table_to_replication(p_driver pgl_ddl_deploy.driver, p_set_name name, p_relation regclass, p_synchronize_data boolean DEFAULT false) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_schema NAME; v_table NAME; v_result BOOLEAN = false; BEGIN IF p_driver = 'pglogical' THEN SELECT pglogical.replication_set_add_table( set_name:=p_set_name ,relation:=p_relation ,synchronize_data:=p_synchronize_data ) INTO v_result; ELSEIF p_driver = 'native' THEN SELECT nspname, relname INTO v_schema, v_table FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.oid = p_relation::OID; EXECUTE 'ALTER PUBLICATION '||quote_ident(p_set_name)||' ADD TABLE '||quote_ident(v_schema)||'.'||quote_ident(v_table)||';'; -- We use true to synchronize data here, not taking the value from p_synchronize_data. This is because of the different way -- that native logical works, and that changes are not queued from the time of the table being added to replication. Thus, we -- by default WILL use COPY_DATA = true -- This needs to be in a DO block currently because of how the DDL is processed on the subscriber. PERFORM pgl_ddl_deploy.replicate_ddl_command($$DO $AUTO_REPLICATE_BLOCK$ BEGIN PERFORM pgl_ddl_deploy.notify_subscription_refresh('$$||p_set_name||$$', true); END$AUTO_REPLICATE_BLOCK$;$$, array[p_set_name]); v_result = true; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; RETURN v_result; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.notify_subscription_refresh(p_set_name name, p_copy_data boolean DEFAULT TRUE) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_rec RECORD; v_sql TEXT; BEGIN IF NOT EXISTS (SELECT 1 FROM pg_subscription WHERE subpublications && array[p_set_name::text]) THEN RAISE EXCEPTION 'No subscription to publication % exists', p_set_name; END IF; FOR v_rec IN SELECT unnest(subpublications) AS pubname, subname FROM pg_subscription WHERE subpublications && array[p_set_name::text] LOOP v_sql = $$ALTER SUBSCRIPTION $$||quote_ident(v_rec.subname)||$$ REFRESH PUBLICATION WITH ( COPY_DATA = '$$||p_copy_data||$$');$$; RAISE LOG 'pgl_ddl_deploy executing: %', v_sql; EXECUTE v_sql; END LOOP; RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_table_wrapper() RETURNS TABLE (id OID, relid REGCLASS, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id); ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id); ELSE RAISE EXCEPTION 'No table pglogical.replication_set_relation or pglogical.replication_set_table found'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_table') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_table r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical' AND tablename = 'replication_set_relation') THEN RETURN QUERY SELECT r.set_id AS id, r.set_reloid AS relid, rs.set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set_relation r JOIN pglogical.replication_set rs USING (set_id) UNION ALL SELECT p.oid AS id, prrelid::REGCLASS AS relid, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p JOIN pg_publication_rel ppr ON ppr.prpubid = p.oid; END IF; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.rep_set_wrapper() RETURNS TABLE (id OID, name NAME, driver pgl_ddl_deploy.driver) LANGUAGE plpgsql SECURITY DEFINER AS $function$ /***** This handles the rename of pglogical.replication_set_relation to pglogical.replication_set_table from version 1 to 2 */ BEGIN IF current_setting('server_version_num')::INT < 100000 THEN IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs; ELSE RAISE EXCEPTION 'pglogical required for version prior to Postgres 10'; END IF; ELSE IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSEIF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'pglogical') THEN RETURN QUERY SELECT set_id AS id, set_name AS name, 'pglogical'::pgl_ddl_deploy.driver AS driver FROM pglogical.replication_set rs UNION ALL SELECT p.oid AS id, pubname AS name, 'native'::pgl_ddl_deploy.driver AS driver FROM pg_publication p; ELSE RAISE EXCEPTION 'Unexpected exception'; END IF; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_count(p_set_config_id integer, p_set_name text, p_include_schema_regex text, p_driver pgl_ddl_deploy.driver) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); BEGIN --If the check is not applicable, pass it IF p_set_config_id IS NULL THEN RETURN TRUE; END IF; SELECT COUNT(1) INTO v_count FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* p_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = p_set_name AND rsr.relid = c.oid AND rsr.driver = p_driver); IF v_count > 0 THEN RAISE WARNING $ERR$ Deployment of auto-replication for id % set_name % failed because % tables are already queued to be added to replication based on your configuration. These tables need to be added to replication manually and synced, otherwise change your configuration. Debug query: %$ERR$, p_set_config_id, p_set_name, v_count, $SQL$ SELECT n.nspname, c.relname FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* '$SQL$||p_include_schema_regex||$SQL$' AND n.nspname !~* '$SQL$||c_exclude_always||$SQL$' AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = '$SQL$||p_set_name||$SQL$' AND rsr.relid = c.oid AND rsr.driver = (SELECT driver FROM pgl_ddl_deploy.set_configs WHERE set_name = '$SQL$||p_set_name||$SQL$')); $SQL$; RETURN FALSE; END IF; RETURN TRUE; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.deployment_check_wrapper(p_set_config_id integer, p_set_name text) RETURNS boolean LANGUAGE plpgsql AS $function$ DECLARE v_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_set_config_id INT; c_include_schema_regex TEXT; v_include_only_repset_tables BOOLEAN; v_ddl_only_replication BOOLEAN; c_set_name TEXT; v_driver pgl_ddl_deploy.driver; BEGIN IF p_set_config_id IS NOT NULL AND p_set_name IS NOT NULL THEN RAISE EXCEPTION 'This function can only be called with one of the two arguments set.'; END IF; IF NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name))) THEN RETURN FALSE; END IF; /*** This check is only applicable to NON-include_only_repset_tables and sets using CREATE TABLE events. It is also bypassed if ddl_only_replication is true in which we never auto-add tables to replication. We re-assign set_config_id because we want to know if no records are found, leading to NULL */ SELECT id, include_schema_regex, set_name, include_only_repset_tables, ddl_only_replication, driver INTO c_set_config_id, c_include_schema_regex, c_set_name, v_include_only_repset_tables, v_ddl_only_replication, v_driver FROM pgl_ddl_deploy.set_configs WHERE ((p_set_name is null and id = p_set_config_id) OR (p_set_config_id is null and set_name = p_set_name)) AND create_tags && '{"CREATE TABLE"}'::TEXT[]; IF v_include_only_repset_tables OR v_ddl_only_replication THEN RETURN TRUE; END IF; RETURN pgl_ddl_deploy.deployment_check_count(c_set_config_id, c_set_name, c_include_schema_regex, v_driver); END; $function$; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.is_subscriber(p_driver pgl_ddl_deploy.driver, p_name TEXT[], p_provider_name NAME = NULL) RETURNS boolean LANGUAGE plpgsql AS $function$ BEGIN IF p_driver = 'pglogical' THEN RETURN EXISTS (SELECT 1 FROM pglogical.subscription s INNER JOIN pglogical.node n ON n.node_id = s.sub_origin AND n.node_name = p_provider_name WHERE sub_replication_sets && p_name); ELSEIF p_driver = 'native' THEN RETURN EXISTS (SELECT 1 FROM pg_subscription s WHERE subpublications && p_name); ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, p_driver pgl_ddl_deploy.driver, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $pgl_ddl_deploy_sql$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN IF pgl_ddl_deploy.is_subscriber(p_driver, p_set_name, p_provider_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $pgl_ddl_deploy_sql$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.queue_ddl_message_type() RETURNS "char" LANGUAGE sql IMMUTABLE AS $function$ SELECT 'Q'::"char"; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.provider_node_name(p_driver pgl_ddl_deploy.driver) RETURNS NAME LANGUAGE plpgsql AS $function$ DECLARE v_node_name NAME; BEGIN IF p_driver = 'pglogical' THEN SELECT n.node_name INTO v_node_name FROM pglogical.node n INNER JOIN pglogical.local_node ln USING (node_id); RETURN v_node_name; ELSEIF p_driver = 'native' THEN RETURN NULL::NAME; ELSE RAISE EXCEPTION 'Unsupported driver specified'; END IF; END; $function$ ; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT sc.id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, sc.driver, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||sc.id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_driver pgl_ddl_deploy.driver = '$BUILD$||sc.driver||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=pgl_ddl_deploy.provider_node_name(c_driver); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; RAISE DEBUG 'v_full_ddl: %', v_full_ddl; RAISE DEBUG 'c_set_config_id: %', c_set_config_id; RAISE DEBUG 'c_set_name: %', c_set_name; RAISE DEBUG 'c_driver: %', c_driver; RAISE DEBUG 'v_ddl_sql_sent: %', v_ddl_sql_sent; v_sql:=$INNER_BLOCK$ SELECT $BUILD$||CASE WHEN sc.driver = 'native' THEN 'pgl_ddl_deploy' WHEN sc.driver = 'pglogical' THEN 'pglogical' ELSE 'ERROR-EXCEPTION' END||$BUILD$.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||COALESCE(quote_literal(c_provider_name), 'NULL')||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$, p_driver := $INNER_BLOCK$||quote_literal(c_driver)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG 'v_sql: %', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = c_set_name AND rsr.relid = c.oid AND rsr.driver = c_driver) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.relid = c.objid AND c.object_type in('table','table column','table constraint') AND rsr.name = '$BUILD$||sc.set_name||$BUILD$' AND rsr.driver = '$BUILD$||sc.driver||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pgl_ddl_deploy.rep_set_wrapper() rs INNER JOIN pgl_ddl_deploy.set_configs sc ON sc.set_name = rs.name AND sc.driver = rs.driver ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN driver = 'pglogical' THEN '--no-op pglogical diver'::TEXT WHEN driver = 'native' THEN $BUILD$ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname = '$BUILD$||set_name||$BUILD$' AND schemaname = 'pgl_ddl_deploy' AND tablename = 'queue') THEN ALTER PUBLICATION $BUILD$||quote_ident(set_name)||$BUILD$ ADD TABLE pgl_ddl_deploy.queue; END IF; END$$; $BUILD$ END AS add_queue_table_to_replication, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pgl_ddl_deploy.add_table_to_replication( p_driver:=c_driver ,p_set_name:=c_set_name ,p_relation:=c.oid ,p_synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| b.add_queue_table_to_replication||$BUILD$ $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.add_role(p_roleoid oid) RETURNS boolean LANGUAGE plpgsql AS $function$ /****** Assuming roles doing DDL are not superusers, this function grants needed privileges to run through the pgl_ddl_deploy DDL deployment. This needs to be run on BOTH provider and subscriber. ******/ DECLARE v_rec RECORD; v_sql TEXT; v_rsat_args TEXT; BEGIN FOR v_rec IN SELECT quote_ident(rolname) AS rolname FROM pg_roles WHERE oid = p_roleoid LOOP v_sql:=' GRANT USAGE ON SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.sql_command_tags(text) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) TO '||v_rec.rolname||'; GRANT INSERT, UPDATE, SELECT ON ALL TABLES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||'; GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgl_ddl_deploy TO '||v_rec.rolname||';'; EXECUTE v_sql; IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN v_rsat_args:=pg_get_function_identity_arguments('pglogical.replication_set_add_table'::REGPROC); v_sql:=' GRANT USAGE ON SCHEMA pglogical TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replicate_ddl_command(text, text[]) TO '||v_rec.rolname||'; GRANT EXECUTE ON FUNCTION pglogical.replication_set_add_table(' || v_rsat_args || ') TO '||v_rec.rolname||'; GRANT SELECT ON ALL TABLES IN SCHEMA pglogical TO '||v_rec.rolname||';'; EXECUTE v_sql; END IF; RETURN true; END LOOP; RETURN false; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; -- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE IF EXISTS ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) FROM PUBLIC; /* pgl_ddl_deploy--2.0--2.1.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ -- This handles potential duplicates with multiple subscriptions to same publisher db. IF EXISTS ( SELECT NEW.* INTERSECT SELECT * FROM pgl_ddl_deploy.queue) THEN RETURN NULL; END IF; IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; /* pgl_ddl_deploy--2.1--2.2.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgl_ddl_deploy" to load this file. \quit /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_views WHERE schemaname = 'pgl_ddl_deploy' AND viewname = 'event_trigger_schema') THEN DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; -- it needs to be modified, so now we drop it to recreate later DROP VIEW pgl_ddl_deploy.event_trigger_schema; ELSE DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT NULL::INT AS id; END IF; END$$; DROP FUNCTION IF EXISTS pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command); DROP FUNCTION IF EXISTS pgl_ddl_deploy.get_altertable_subcmdtypes(pg_ddl_command); CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ -- This handles potential duplicates with multiple subscriptions to same publisher db. IF EXISTS ( SELECT NEW.* INTERSECT SELECT * FROM pgl_ddl_deploy.queue) THEN RETURN NULL; END IF; IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command) RETURNS text[] IMMUTABLE STRICT AS '$libdir/ddl_deparse', 'get_altertable_subcmdinfo' LANGUAGE C; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, p_driver pgl_ddl_deploy.driver, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN = FALSE ) RETURNS BOOLEAN AS $function$ /**** This function is what will actually be executed on the subscriber when attempting to apply DDL changed. It is sent to subscriber(s) via pglogical.replicate_ddl_command. You can see how it is called based on the the view pgl_ddl_deploy.event_trigger_schema, which is used to create the specific event trigger functions that will call this function in different ways depending on configuration in pgl_ddl_deploy.set_configs. This function is also used to make testing easier. The regression suite calls this function to verify basic functionality. ****/ DECLARE v_succeeded BOOLEAN; v_error_message TEXT; v_attempt_number INT = 0; v_signal pgl_ddl_deploy.signals; BEGIN IF pgl_ddl_deploy.is_subscriber(p_driver, p_set_name, p_provider_name) OR p_run_anywhere THEN v_error_message = NULL; /**** If we have configured to kill blocking subscribers, here we set parameters for that: 1. Whether to cancel or terminate 2. What lock_timeout to tolerate ****/ IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN v_signal = CASE WHEN p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' THEN 'cancel' ELSE p_signal_blocking_subscriber_sessions END; -- We cannot RESET LOCAL lock_timeout but that should not be necessary because it will end with the transaction EXECUTE format('SET LOCAL lock_timeout TO %s', p_lock_timeout); END IF; /**** Loop until one of the following takes place: 1. Successful DDL execution on first attempt 2. An unexpected ERROR occurs, which will either RAISE or finish with WARNING based on queue_subscriber_failures configuration 3. Blocking sessions are killed until we finally get a successful DDL execution ****/ WHILE TRUE LOOP BEGIN --Execute DDL RAISE LOG 'pgl_ddl_deploy attempting execution: %', p_full_ddl; --Execute DDL - the reason we use execute here is partly to handle no trailing semicolon EXECUTE p_full_ddl; v_succeeded = TRUE; EXIT; EXCEPTION WHEN lock_not_available THEN IF p_signal_blocking_subscriber_sessions IS NOT NULL THEN -- Change to terminate if we are using cancel_then_terminate and have not been successful after the first iteration IF v_attempt_number > 0 AND p_signal_blocking_subscriber_sessions = 'cancel_then_terminate' AND v_signal = 'cancel' THEN v_signal = 'terminate'; END IF; INSERT INTO pgl_ddl_deploy.killed_blockers (signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported) SELECT signal, successful, pid, executed_at, usename, client_addr, xact_start, state_change, state, query, reported FROM pgl_ddl_deploy.kill_blockers( v_signal, p_nspname, p_relname ); -- Continue and retry again but allow a brief pause v_attempt_number = v_attempt_number + 1; PERFORM pg_sleep(3); ELSE -- If p_signal_blocking_subscriber_sessions is not configured but we hit a lock_timeout, -- then the replication user or cluster is configured with a global lock_timeout. Raise in this case. RAISE; END IF; WHEN OTHERS THEN IF p_queue_subscriber_failures THEN RAISE WARNING 'Subscriber DDL failed with errors (see pgl_ddl_deploy.subscriber_logs): %', SQLERRM; v_succeeded = FALSE; v_error_message = SQLERRM; EXIT; ELSE RAISE; END IF; END; END LOOP; /**** Since this function is only executed on the subscriber, this INSERT adds a log to subscriber_logs on the subscriber after execution. Note that if we configured queue_subscriber_failures to TRUE in pgl_ddl_deploy.set_configs, then we are allowing failed DDL to be caught and logged in this table as succeeded = FALSE for later processing. ****/ INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (p_set_name, p_pid, p_provider_name, p_set_config_id, current_role, pg_backend_pid(), current_timestamp, p_ddl_sql_sent, p_full_ddl, v_succeeded, v_error_message); END IF; RETURN v_succeeded; END; $function$ LANGUAGE plpgsql VOLATILE; CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT sc.id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, sc.driver, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||sc.id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_driver pgl_ddl_deploy.driver = '$BUILD$||sc.driver||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=pgl_ddl_deploy.provider_node_name(c_driver); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; RAISE DEBUG 'v_full_ddl: %', v_full_ddl; RAISE DEBUG 'c_set_config_id: %', c_set_config_id; RAISE DEBUG 'c_set_name: %', c_set_name; RAISE DEBUG 'c_driver: %', c_driver; RAISE DEBUG 'v_ddl_sql_sent: %', v_ddl_sql_sent; v_sql:=$INNER_BLOCK$ SELECT $BUILD$||CASE WHEN sc.driver = 'native' THEN 'pgl_ddl_deploy' WHEN sc.driver = 'pglogical' THEN 'pglogical' ELSE 'ERROR-EXCEPTION' END||$BUILD$.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||COALESCE(quote_literal(c_provider_name), 'NULL')||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$, p_driver := $INNER_BLOCK$||quote_literal(c_driver)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG 'v_sql: %', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = c_set_name AND rsr.relid = c.oid AND rsr.driver = c_driver) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.relid = c.objid AND c.object_type in('table','table column','table constraint') AND rsr.name = '$BUILD$||sc.set_name||$BUILD$' AND rsr.driver = '$BUILD$||sc.driver||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pgl_ddl_deploy.rep_set_wrapper() rs INNER JOIN pgl_ddl_deploy.set_configs sc ON sc.set_name = rs.name AND sc.driver = rs.driver ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN driver = 'pglogical' THEN '--no-op pglogical diver'::TEXT WHEN driver = 'native' THEN $BUILD$ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname = '$BUILD$||set_name||$BUILD$' AND schemaname = 'pgl_ddl_deploy' AND tablename = 'queue') THEN ALTER PUBLICATION $BUILD$||quote_ident(set_name)||$BUILD$ ADD TABLE pgl_ddl_deploy.queue; END IF; END$$; $BUILD$ END AS add_queue_table_to_replication, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pgl_ddl_deploy.add_table_to_replication( p_driver:=c_driver ,p_set_name:=c_set_name ,p_relation:=c.oid ,p_synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| b.add_queue_table_to_replication||$BUILD$ $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b; -- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE IF EXISTS ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) FROM PUBLIC; pgl_ddl_deploy-2.2.1/pgl_ddl_deploy-sql-maker.sh000077500000000000000000000022011453171545300217120ustar00rootroot00000000000000#!/usr/bin/env bash set -eu last_version=2.1 new_version=2.2 last_version_file=pgl_ddl_deploy--${last_version}.sql new_version_file=pgl_ddl_deploy--${new_version}.sql update_file=pgl_ddl_deploy--${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 pgl_ddl_deploy" 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 schema/2.2.sql $update_file add_file functions/execute_queued_ddl.sql $update_file add_file functions/get_altertable_subcmdinfo.sql $update_file add_file functions/subscriber_command.sql $update_file add_file views/event_trigger_schema.sql $update_file add_file schema/2.2_post.sql $update_file # Only copy diff and new files after last version, and add the update script cp $last_version_file $new_version_file cat $update_file >> $new_version_file pgl_ddl_deploy-2.2.1/pgl_ddl_deploy.c000066400000000000000000000053321453171545300176350ustar00rootroot00000000000000#include "postgres.h" #include "fmgr.h" #include "catalog/pg_type.h" #include "tcop/utility.h" #include "utils/builtins.h" #include "parser/parser.h" PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(pgl_ddl_deploy_current_query); PG_FUNCTION_INFO_V1(sql_command_tags); /* Our own version of debug_query_string - see below */ const char *pgl_ddl_deploy_debug_query_string; /* * A near-copy of the current_query postgres function which caches the value, ignoring * the change if the string is changed to NULL, as is being done in pglogical 2.2.2. * This allows multiple subsequent calls to pglogical.replicate_ddl_command without * losing access to current_query. * * Please revisit if pglogical changes behavior and stop setting debug_query_string to NULL. */ Datum pgl_ddl_deploy_current_query(PG_FUNCTION_ARGS) { /* If debug_query_string is set, we always want the same value */ if (debug_query_string) { pgl_ddl_deploy_debug_query_string = debug_query_string; PG_RETURN_TEXT_P(cstring_to_text(pgl_ddl_deploy_debug_query_string)); } /* If it is NULL, we want to take pgl_ddl_deploy_debug_query_string instead, which in most cases in this code path is used in pgl_ddl_deploy we expect is because pglogical has reset the string to null. But we still want to return the same value in this SQL statement we are executing. */ else if (pgl_ddl_deploy_debug_query_string) { PG_RETURN_TEXT_P(cstring_to_text(pgl_ddl_deploy_debug_query_string)); } else /* If both are NULL, that is legit and we want to return NULL. */ { PG_RETURN_NULL(); } } /* * Return a text array of the command tags in SQL command */ Datum sql_command_tags(PG_FUNCTION_ARGS) { text *sql_t = PG_GETARG_TEXT_P(0); char *sql; List *parsetree_list; ListCell *parsetree_item; const char *commandTag; ArrayBuildState *astate = NULL; /* * Get the SQL parsetree */ sql = text_to_cstring(sql_t); parsetree_list = pg_parse_query(sql); /* * Iterate through each parsetree_item to get CommandTag */ foreach(parsetree_item, parsetree_list) { Node *parsetree = (Node *) lfirst(parsetree_item); #if PG_VERSION_NUM >= 130000 commandTag = CreateCommandName(parsetree); #else commandTag = CreateCommandTag(parsetree); #endif astate = accumArrayResult(astate, CStringGetTextDatum(commandTag), false, TEXTOID, CurrentMemoryContext); } if (astate == NULL) elog(ERROR, "Invalid sql command"); PG_RETURN_ARRAYTYPE_P(DatumGetPointer(makeArrayResult(astate, CurrentMemoryContext))); } pgl_ddl_deploy-2.2.1/pgl_ddl_deploy.control000066400000000000000000000002551453171545300210720ustar00rootroot00000000000000# pgl_ddl_deploy extension comment = 'automated ddl deployment using pglogical' module_pathname = '$libdir/pgl_ddl_deploy' default_version = '2.2' schema = 'pgl_ddl_deploy' pgl_ddl_deploy-2.2.1/schema/000077500000000000000000000000001453171545300157455ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/schema/1.4.sql000066400000000000000000000015251453171545300167730ustar00rootroot00000000000000DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; SELECT pgl_ddl_deploy.drop_ext_object('FUNCTION','pgl_ddl_deploy.dependency_update()'); DROP FUNCTION pgl_ddl_deploy.dependency_update(); SELECT pgl_ddl_deploy.drop_ext_object('VIEW','pgl_ddl_deploy.rep_set_table_wrapper'); DROP VIEW IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN exclude_alter_table_subcommands TEXT[]; ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_only_alter_table; SELECT pg_catalog.pg_extension_config_dump('pgl_ddl_deploy.set_configs_id_seq', ''); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN ddl_only_replication BOOLEAN NOT NULL DEFAULT FALSE; pgl_ddl_deploy-2.2.1/schema/1.4_post.sql000066400000000000000000000005431453171545300200370ustar00rootroot00000000000000ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT repset_tables_restricted_tags CHECK ((NOT include_only_repset_tables) OR (include_only_repset_tables AND pgl_ddl_deploy.standard_repset_only_tags() @> create_tags AND drop_tags IS NULL)); SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; pgl_ddl_deploy-2.2.1/schema/1.5.sql000066400000000000000000000040701453171545300167720ustar00rootroot00000000000000ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN include_everything BOOLEAN NOT NULL DEFAULT FALSE; -- Now we have 3 configuration types ALTER TABLE pgl_ddl_deploy.set_configs DROP CONSTRAINT repset_tables_or_regex_inclusion; -- Only allow one of them to be chosen ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT single_configuration_type CHECK ((include_schema_regex IS NOT NULL AND NOT include_only_repset_tables) OR (include_only_repset_tables AND include_schema_regex IS NULL) OR (include_everything AND NOT include_only_repset_tables AND include_schema_regex IS NULL)); ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT ddl_only_restrictions CHECK (NOT (ddl_only_replication AND include_only_repset_tables)); -- Need to adjust to after trigger and change function def DROP TRIGGER unique_tags ON pgl_ddl_deploy.set_configs; DROP FUNCTION pgl_ddl_deploy.unique_tags(); -- We need to add the column include_everything to it in a nice order DROP VIEW pgl_ddl_deploy.event_trigger_schema; -- Support canceling or terminating blocking processes on subscriber CREATE TYPE pgl_ddl_deploy.signals AS ENUM ('cancel','terminate','cancel_then_terminate'); ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN signal_blocking_subscriber_sessions pgl_ddl_deploy.signals; ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN subscriber_lock_timeout INT; ALTER TABLE pgl_ddl_deploy.set_configs ADD CONSTRAINT valid_signal_blocker_config CHECK (NOT (lock_safe_deployment AND (signal_blocking_subscriber_sessions IS NOT NULL OR subscriber_lock_timeout IS NOT NULL)) AND NOT (subscriber_lock_timeout IS NOT NULL AND signal_blocking_subscriber_sessions IS NULL)); CREATE TABLE pgl_ddl_deploy.killed_blockers ( id SERIAL PRIMARY KEY, signal TEXT, successful BOOLEAN, pid INT, executed_at TIMESTAMPTZ, usename NAME, client_addr INET, xact_start TIMESTAMPTZ, state_change TIMESTAMPTZ, state TEXT, query TEXT, reported BOOLEAN DEFAULT FALSE, reported_at TIMESTAMPTZ ); pgl_ddl_deploy-2.2.1/schema/1.5_post.sql000066400000000000000000000010331453171545300200330ustar00rootroot00000000000000-- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; pgl_ddl_deploy-2.2.1/schema/1.6.sql000066400000000000000000000014621453171545300167750ustar00rootroot00000000000000CREATE FUNCTION pgl_ddl_deploy.current_query() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgl_ddl_deploy_current_query' LANGUAGE C VOLATILE STRICT; -- Drop UPDATE event for this trigger, which leads to unexpected behavior DROP TRIGGER set_tag_defaults ON pgl_ddl_deploy.set_configs; CREATE TRIGGER set_tag_defaults BEFORE INSERT ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.set_tag_defaults(); /* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; pgl_ddl_deploy-2.2.1/schema/1.6_post.sql000066400000000000000000000002751453171545300200430ustar00rootroot00000000000000-- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; pgl_ddl_deploy-2.2.1/schema/2.0.sql000066400000000000000000000100121453171545300167570ustar00rootroot00000000000000/* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_views WHERE schemaname = 'pgl_ddl_deploy' AND viewname = 'event_trigger_schema') THEN DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; ELSE DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT NULL::INT AS id; END IF; END$$; CREATE TYPE pgl_ddl_deploy.driver AS ENUM ('pglogical', 'native'); -- Not possible that any existing config would be native, so: ALTER TABLE pgl_ddl_deploy.set_configs ADD COLUMN driver pgl_ddl_deploy.driver NOT NULL DEFAULT 'pglogical'; DROP FUNCTION IF EXISTS pgl_ddl_deploy.rep_set_table_wrapper(); DROP FUNCTION IF EXISTS pgl_ddl_deploy.deployment_check_count(integer, text, text); DROP FUNCTION pgl_ddl_deploy.subscriber_command ( p_provider_name NAME, p_set_name TEXT[], p_nspname NAME, p_relname NAME, p_ddl_sql_sent TEXT, p_full_ddl TEXT, p_pid INT, p_set_config_id INT, p_queue_subscriber_failures BOOLEAN, p_signal_blocking_subscriber_sessions pgl_ddl_deploy.signals, p_lock_timeout INT, -- This parameter currently only exists to make testing this function easier p_run_anywhere BOOLEAN ); CREATE TABLE pgl_ddl_deploy.queue( queued_at timestamp with time zone not null, role name not null, pubnames text[], message_type "char" not null, message text not null ); COMMENT ON TABLE pgl_ddl_deploy.queue IS 'Modeled on the pglogical.queue table for native logical replication ddl'; ALTER TABLE pgl_ddl_deploy.queue REPLICA IDENTITY FULL; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN FALSE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; -- NOTE - this duplicates execute_queued_ddl.sql function file but is executed here for the upgrade/build path CREATE OR REPLACE FUNCTION pgl_ddl_deploy.execute_queued_ddl() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN /*** Native logical replication does not support row filtering, so as a result, we need to do processing downstream to ensure we only process rows we care about. For example, if we propagate some DDL to system 1 and some other to system 2, all rows will still come through this trigger. We filter out rows based on matching pubnames with pg_subscription.subpublications If a row arrives here (the subscriber), it must mean that it was propagated ***/ IF NEW.message_type = pgl_ddl_deploy.queue_ddl_message_type() AND (pgl_ddl_deploy.override() OR ((SELECT COUNT(1) FROM pg_subscription s WHERE subpublications && NEW.pubnames) > 0)) THEN -- See https://www.postgresql.org/message-id/CAMa1XUh7ZVnBzORqjJKYOv4_pDSDUCvELRbkF0VtW7pvDW9rZw@mail.gmail.com IF NEW.message ~* 'pgl_ddl_deploy.notify_subscription_refresh' THEN INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES (NEW.pubnames[1], NULL, NULL, NULL, current_role, pg_backend_pid(), current_timestamp, NEW.message, NEW.message, FALSE, 'Unsupported automated ALTER SUBSCRIPTION ... REFRESH PUBLICATION until bugfix'); ELSE EXECUTE 'SET ROLE '||quote_ident(NEW.role)||';'; EXECUTE NEW.message::TEXT; END IF; RETURN NEW; ELSE RETURN NULL; END IF; END; $function$ ; CREATE TRIGGER execute_queued_ddl BEFORE INSERT ON pgl_ddl_deploy.queue FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.execute_queued_ddl(); -- This must only fire on the replica ALTER TABLE pgl_ddl_deploy.queue ENABLE REPLICA TRIGGER execute_queued_ddl; pgl_ddl_deploy-2.2.1/schema/2.0_post.sql000066400000000000000000000020571453171545300200360ustar00rootroot00000000000000-- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE IF EXISTS ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) FROM PUBLIC; pgl_ddl_deploy-2.2.1/schema/2.2.sql000066400000000000000000000016511453171545300167720ustar00rootroot00000000000000/* * We need to re-deploy the trigger function definitions * which will have changed with this extension update. So * here we undeploy them, and save which ones we need to * recreate later. */ DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_views WHERE schemaname = 'pgl_ddl_deploy' AND viewname = 'event_trigger_schema') THEN DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT id, pgl_ddl_deploy.undeploy(id) AS undeployed FROM pgl_ddl_deploy.event_trigger_schema WHERE is_deployed; -- it needs to be modified, so now we drop it to recreate later DROP VIEW pgl_ddl_deploy.event_trigger_schema; ELSE DROP TABLE IF EXISTS ddl_deploy_to_refresh; CREATE TEMP TABLE ddl_deploy_to_refresh AS SELECT NULL::INT AS id; END IF; END$$; DROP FUNCTION IF EXISTS pgl_ddl_deploy.get_altertable_subcmdinfo(pg_ddl_command); DROP FUNCTION IF EXISTS pgl_ddl_deploy.get_altertable_subcmdtypes(pg_ddl_command); pgl_ddl_deploy-2.2.1/schema/2.2_post.sql000066400000000000000000000020571453171545300200400ustar00rootroot00000000000000-- Now re-deploy event triggers and functions SELECT id, pgl_ddl_deploy.deploy(id) AS deployed FROM ddl_deploy_to_refresh; DROP TABLE IF EXISTS ddl_deploy_to_refresh; DROP TABLE IF EXISTS tmp_objs; -- Ensure added roles have write permissions for new tables added -- Not so easy to pre-package this with default privileges because -- we can't assume everyone uses the same role to deploy this extension SELECT pgl_ddl_deploy.add_role(role_oid) FROM ( SELECT DISTINCT r.oid AS role_oid FROM information_schema.table_privileges tp INNER JOIN pg_roles r ON r.rolname = tp.grantee AND NOT r.rolsuper WHERE table_schema = 'pgl_ddl_deploy' AND privilege_type = 'INSERT' AND table_name = 'subscriber_logs' ) roles_with_existing_privileges; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.add_table_to_replication(pgl_ddl_deploy.driver, name, regclass, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.notify_subscription_refresh(name, boolean) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pgl_ddl_deploy.kill_blockers(pgl_ddl_deploy.signals, name, name) FROM PUBLIC; pgl_ddl_deploy-2.2.1/sql/000077500000000000000000000000001453171545300153045ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/sql/01_create_ext.sql000066400000000000000000000003041453171545300204450ustar00rootroot00000000000000-- Allow running regression suite with upgrade paths \set v `echo ${FROMVERSION:-2.2}` SET client_min_messages = warning; CREATE EXTENSION pglogical; CREATE EXTENSION pgl_ddl_deploy VERSION :'v'; pgl_ddl_deploy-2.2.1/sql/02_setup.sql000066400000000000000000000050531453171545300174710ustar00rootroot00000000000000CREATE TEMP TABLE foonode AS SELECT pglogical.create_node('test','host=localhost'); DROP TABLE foonode; CREATE TEMP TABLE repsets AS 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 FROM sets s; DROP TABLE repsets; CREATE ROLE test_pgl_ddl_deploy LOGIN; GRANT CREATE ON DATABASE contrib_regression TO test_pgl_ddl_deploy; GRANT CREATE ON SCHEMA public TO PUBLIC; SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname = 'test_pgl_ddl_deploy'; SET ROLE test_pgl_ddl_deploy; CREATE FUNCTION check_rep_tables() RETURNS TABLE (set_name TEXT, table_name TEXT) AS $BODY$ BEGIN -- Handle change from view to function rep_set_table_wrapper IF (SELECT extversion FROM pg_extension WHERE extname = 'pgl_ddl_deploy') = ANY('{1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7}'::text[]) THEN RETURN QUERY EXECUTE $$ SELECT set_name::TEXT, set_reloid::TEXT AS table_name FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) ORDER BY set_name::TEXT, set_reloid::TEXT;$$; ELSE RETURN QUERY EXECUTE $$ SELECT name::TEXT AS set_name, relid::regclass::TEXT AS table_name FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE relid::regclass::TEXT <> 'pgl_ddl_deploy.queue' ORDER BY name::TEXT, relid::TEXT;$$; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION all_queues() RETURNS TABLE (queued_at timestamp with time zone, role name, pubnames text[], message_type "char", -- we use json here to provide test output consistency whether native or pglogical message json) AS $BODY$ BEGIN IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY EXECUTE $$ SELECT queued_at, role, replication_sets AS pubnames, message_type, message FROM pglogical.queue UNION ALL SELECT queued_at, role, pubnames, message_type, to_json(message) AS message FROM pgl_ddl_deploy.queue;$$; ELSE RETURN QUERY EXECUTE $$ SELECT queued_at, role, pubnames, message_type, to_json(message) AS message FROM pgl_ddl_deploy.queue; $$; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION verify_count(ct int, expected int) RETURNS BOOLEAN AS $BODY$ BEGIN RAISE LOG 'ct: %', ct; IF ct != expected THEN RAISE EXCEPTION 'Count % does not match expected count of %', ct, expected; END IF; RETURN TRUE; END$BODY$ LANGUAGE plpgsql; pgl_ddl_deploy-2.2.1/sql/03_add_configs.sql000066400000000000000000000026261453171545300205750ustar00rootroot00000000000000INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test1','.*',true, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test2','.*',true, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test3','.*',false, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test4','.*',false, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test5','^foo.*',true, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test6','^foo.*',true, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test7','^foo.*',false, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test8','^foo.*',false, false); --Ensure regex must be valid INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test9','^foo.*((',false, false); pgl_ddl_deploy-2.2.1/sql/04_deploy.sql000066400000000000000000000020471453171545300176270ustar00rootroot00000000000000--These will show different warnings depending on version SET client_min_messages = error; \set VERBOSITY TERSE /*** No deploy allowed if table would be added to replication ***/ SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key); SET ROLE postgres; SELECT pgl_ddl_deploy.deploy('test1'); SET ROLE test_pgl_ddl_deploy; DROP TABLE foo; SET ROLE postgres; --This should work now SELECT pgl_ddl_deploy.deploy('test1'); --This should work SELECT pgl_ddl_deploy.disable('test1'); --This should not work SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key); SET ROLE postgres; SELECT pgl_ddl_deploy.enable('test1'); SET ROLE test_pgl_ddl_deploy; DROP TABLE foo; SET ROLE postgres; --This should work now SELECT pgl_ddl_deploy.enable('test1'); --Enable all the rest DO $$ DECLARE v_rec RECORD; BEGIN FOR v_rec IN SELECT DISTINCT set_name FROM pgl_ddl_deploy.set_configs WHERE set_name LIKE 'test%' AND set_name <> 'test1' ORDER BY set_name LOOP PERFORM pgl_ddl_deploy.deploy(v_rec.set_name); END LOOP; END$$; pgl_ddl_deploy-2.2.1/sql/04_deploy_update.sql000066400000000000000000000020011453171545300211570ustar00rootroot00000000000000--This will show different warnings depending on if we are actually updating to new version or not SET client_min_messages = error; ALTER EXTENSION pgl_ddl_deploy UPDATE; SELECT pgl_ddl_deploy.deploy('test1'); DO $$ DECLARE v_rec RECORD; BEGIN FOR v_rec IN SELECT name FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name LIKE 'test%' AND name <> 'test1' ORDER BY name LOOP PERFORM pgl_ddl_deploy.deploy(v_rec.name); END LOOP; END$$; --Now that we are on highest version, ensure WARNING shows CREATE TEMP TABLE repset AS SELECT pglogical.create_replication_set (set_name:='testtemp' ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE); DROP TABLE repset; SET client_min_messages = warning; BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (id, set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES (999, 'testtemp','.*',true, true); CREATE TABLE break(id serial primary key); SELECT pgl_ddl_deploy.deploy('testtemp'); ROLLBACK; pgl_ddl_deploy-2.2.1/sql/05_allowed.sql000066400000000000000000000074601453171545300177670ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; SET client_min_messages TO warning; /*** In default schema **/ CREATE TABLE foo(id serial primary key); SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER TABLE foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; INSERT INTO foo (bla) VALUES (1),(2),(3); DROP TABLE foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE FUNCTION foo() RETURNS INT AS $BODY$ SELECT 1; $BODY$ LANGUAGE SQL STABLE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER FUNCTION foo() OWNER TO current_user; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP FUNCTION foo(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE VIEW fooview AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER VIEW fooview RENAME TO barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP VIEW barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE SEQUENCE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER SEQUENCE foo RESTART; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP SEQUENCE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE SCHEMA foobar; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE TABLE foobar.foo(id serial primary key); SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER TABLE foobar.foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; INSERT INTO foobar.foo (bla) VALUES (1),(2),(3); DROP TABLE foobar.foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE FUNCTION foobar.foo() RETURNS INT AS $BODY$ SELECT 1; $BODY$ LANGUAGE SQL STABLE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER FUNCTION foobar.foo() OWNER TO current_user; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP FUNCTION foobar.foo(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE VIEW foobar.fooview AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER VIEW foobar.fooview RENAME TO barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP VIEW foobar.barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE SEQUENCE foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER SEQUENCE foobar.foo RESTART; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP SEQUENCE foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP SCHEMA foobar CASCADE; SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT * FROM pgl_ddl_deploy.unhandled; SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/06_multi.sql000066400000000000000000000063461453171545300174750ustar00rootroot00000000000000SET log_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA foobar; --This should never be allowed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo;" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo;" SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foo(id int primary key); COMMIT;" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT;" SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; --Run all commands through cli to avoid permissions issues \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo CASCADE;" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foobar.foo CASCADE;" --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;" SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key);" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key);" --This should be allowed by some but not others \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo, foobar.foo CASCADE;" SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; --Resolutions SELECT pgl_ddl_deploy.resolve_unhandled(id, 'DBA superhero deployed it manually on the subscribers!') FROM pgl_ddl_deploy.unhandled; --Test with no rows and a dummy row SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; BEGIN; INSERT INTO pgl_ddl_deploy.exceptions (set_name) VALUES ('test1'); SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; ROLLBACK; SELECT resolved, resolved_notes, set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/07_edges.sql000066400000000000000000000007231453171545300174240ustar00rootroot00000000000000SET client_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY); CREATE TABLE foo (id SERIAL PRIMARY KEY); --This is an edge case that currently can't be dealt with well with filtered replication. ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP TABLE foobar.foo CASCADE; DROP TABLE foo CASCADE; pgl_ddl_deploy-2.2.1/sql/08_ignored.sql000066400000000000000000000005271453171545300177670ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; CREATE TEMP TABLE foo(id SERIAL PRIMARY KEY); ALTER TABLE foo ADD COLUMN bla TEXT; DROP TABLE foo; SELECT 1 AS myfield INTO TEMP foo; DROP TABLE foo; CREATE TEMP TABLE foo AS SELECT 1 AS myfield; DROP TABLE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; pgl_ddl_deploy-2.2.1/sql/09_unsupported.sql000066400000000000000000000012211453171545300207210ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; SELECT 1 AS myfield INTO foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; DROP TABLE foo; DROP TABLE foobar.foo; SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/10_no_create_user.sql000066400000000000000000000003251453171545300213220ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE CREATE ROLE test_pgl_ddl_deploy_nopriv; SET ROLE test_pgl_ddl_deploy_nopriv; CREATE TEMP TABLE bla (id serial primary key); DROP TABLE bla; RESET ROLE; pgl_ddl_deploy-2.2.1/sql/11_override.sql000066400000000000000000000005051453171545300201450ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE SET SESSION_REPLICATION_ROLE TO REPLICA; CREATE TABLE i_want_to_ignore_evts (id serial primary key); DROP TABLE i_want_to_ignore_evts; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; RESET SESSION_REPLICATION_ROLE; pgl_ddl_deploy-2.2.1/sql/12_sql_command_tags.sql000066400000000000000000000004211453171545300216370ustar00rootroot00000000000000SELECT pgl_ddl_deploy.sql_command_tags(NULL); SELECT pgl_ddl_deploy.sql_command_tags(''); SELECT pgl_ddl_deploy.sql_command_tags('CREATE EXTENSON foo;'); SELECT pgl_ddl_deploy.sql_command_tags('CREATE TABLE foo(); ALTER TABLE foo ADD COLUMN bar text; DROP TABLE foo;'); pgl_ddl_deploy-2.2.1/sql/13_transaction.sql000066400000000000000000000021461453171545300206600ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; SET client_min_messages TO warning; BEGIN; /*** In default schema **/ CREATE TABLE foo(id serial primary key); SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER TABLE foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; INSERT INTO foo (bla) VALUES (1),(2),(3); DROP TABLE foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE TABLE foobar.foo(id serial primary key); SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER TABLE foobar.foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; INSERT INTO foobar.foo (bla) VALUES (1),(2),(3); DROP TABLE foobar.foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; COMMIT; SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/15_new_set_behavior.sql000066400000000000000000000071401453171545300216570ustar00rootroot00000000000000SET client_min_messages = warning; \set VERBOSITY terse --This should fail due to overlapping tags INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'test1', '.*', TRUE, TRUE, FALSE, '{"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"}', '{"DROP VIEW","DROP FUNCTION"}'; --But if we drop these tags from test1, it should work UPDATE pgl_ddl_deploy.set_configs SET create_tags = '{ALTER TABLE,CREATE SEQUENCE,ALTER SEQUENCE,CREATE SCHEMA,CREATE TABLE,CREATE TYPE,ALTER TYPE}', drop_tags = '{DROP SCHEMA,DROP TABLE,DROP TYPE,DROP SEQUENCE}' WHERE set_name = 'test1'; --Now this set will only handle these tags INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'test1', '.*', TRUE, TRUE, FALSE, '{"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"}', '{"DROP VIEW","DROP FUNCTION"}'; --include_only_repset_tables CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('my_special_tables_1'::TEXT), ('my_special_tables_2'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; --Only ALTER TABLE makes sense (and is allowed) with include_only_repset_tables. So this should fail INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"CREATE TABLE"}'; --This is OK INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'temp_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; DELETE FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; --This also should fail - no DROP tags at all allowed INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', '{"DROP TABLE"}'; --These both are OK INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_2', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; --Check we get the defaults we want from the trigger BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex) VALUES ('temp_1', '.*'); SELECT create_tags, drop_tags FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; ROLLBACK; BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_only_repset_tables) VALUES ('temp_1', TRUE); SELECT create_tags, drop_tags FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; ROLLBACK; --Now deploy again separately --By set_name: SELECT pgl_ddl_deploy.deploy('test1'); --By set_config_id SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; pgl_ddl_deploy-2.2.1/sql/16_multi_set_tags.sql000066400000000000000000000014601453171545300213570ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA viewer; --Should be handled by separate set_config CREATE TABLE viewer.foo(id int primary key); --Should be handled by separate set_config CREATE VIEW viewer.vw_foo AS SELECT * FROM viewer.foo; SELECT c.create_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name = 'test1' ORDER BY e.id DESC LIMIT 4; DROP VIEW viewer.vw_foo; DROP TABLE viewer.foo CASCADE; DROP SCHEMA viewer; SELECT c.drop_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name = 'test1' ORDER BY e.id DESC LIMIT 4; SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/17_include_only_repset_tables_1.sql000066400000000000000000000006061453171545300241560ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; --These kinds of repsets will not replicate CREATE events, only ALTER TABLE, so deploy after CREATE --We assume schema will be copied to subscriber separately CREATE SCHEMA special; CREATE TABLE special.foo (id serial primary key, foo text, bar text); CREATE TABLE special.bar (id serial primary key, super text, man text); pgl_ddl_deploy-2.2.1/sql/18_include_only_repset_tables_2.sql000066400000000000000000000021671453171545300241640ustar00rootroot00000000000000SET client_min_messages = warning; SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.foo'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_1'; SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.bar'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_2'; --Deploy by set_name SELECT pgl_ddl_deploy.deploy('my_special_tables_1'); SELECT pgl_ddl_deploy.deploy('my_special_tables_2'); --Ensure these kinds of configs only have 'create' event triggers SELECT COUNT(1) FROM pg_event_trigger evt INNER JOIN pgl_ddl_deploy.event_trigger_schema ets ON evt.evtname IN(auto_replication_unsupported_trigger_name, ets.auto_replication_drop_trigger_name, ets.auto_replication_create_trigger_name) WHERE include_only_repset_tables; --Deploy by id SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'my_special_tables_1'; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'my_special_tables_2'; pgl_ddl_deploy-2.2.1/sql/19_include_only_repset_tables_3.sql000066400000000000000000000020341453171545300241570ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; ALTER TABLE special.foo ADD COLUMN happy TEXT; ALTER TABLE special.bar ADD COLUMN happier TEXT; SELECT c.create_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; --Test renaming which was missing in 1.2 ALTER TABLE special.foo RENAME COLUMN happy to happyz; ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz; ALTER TABLE special.foo RENAME COLUMN id TO id_2; ALTER TABLE special.bar RENAME COLUMN happier TO happierz; ALTER TABLE special.bar RENAME COLUMN id TO id_3; ALTER TABLE special.foo RENAME TO fooz; ALTER TABLE special.bar RENAME TO barz; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 20; pgl_ddl_deploy-2.2.1/sql/20_include_only_repset_tables_4.sql000066400000000000000000000031071453171545300241520ustar00rootroot00000000000000SET client_min_messages = warning; CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$ BEGIN RETURN NULL; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER noop BEFORE DELETE ON special.fooz FOR EACH ROW EXECUTE PROCEDURE noop(); ALTER TABLE special.fooz DISABLE TRIGGER noop; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; -- Test new subcommand functionality UPDATE pgl_ddl_deploy.set_configs SET exclude_alter_table_subcommands = pgl_ddl_deploy.common_exclude_alter_table_subcommands() WHERE include_only_repset_tables; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE include_only_repset_tables; SET client_min_messages = log; -- This should be ignored ALTER TABLE special.fooz ENABLE TRIGGER noop; -- This contains a tag we want to ignore but we can't separate out the parts - see the warning message ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); ALTER TABLE special.fooz ADD COLUMN bar_id INT; -- This one should be ignored as well ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; SET client_min_messages = warning; DROP TABLE special.fooz CASCADE; DROP TABLE special.barz CASCADE; DROP SCHEMA special; pgl_ddl_deploy-2.2.1/sql/21_unprivileged_users.sql000066400000000000000000000001411453171545300222410ustar00rootroot00000000000000CREATE ROLE unpriv; SET ROLE unpriv; CREATE TEMP TABLE foo(); ALTER TABLE foo ADD COLUMN id INT; pgl_ddl_deploy-2.2.1/sql/22_is_deployed.sql000066400000000000000000000015701453171545300206330ustar00rootroot00000000000000SET client_min_messages TO warning; --Test what is_deployed shows (introduced in 1.3) SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; SELECT pgl_ddl_deploy.undeploy(id) FROM pgl_ddl_deploy.set_configs; SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; --Nothing should replicate this CREATE TABLE foobar (id serial primary key); DROP TABLE foobar; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; --Re-deploy and check again what shows as deployed SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs; SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; CREATE TABLE foobar (id serial primary key); DROP TABLE foobar CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; pgl_ddl_deploy-2.2.1/sql/23_1_4_features.sql000066400000000000000000000034521453171545300206160ustar00rootroot00000000000000SET client_min_messages = warning; -- We need to eventually test this on a real subscriber SET search_path TO ''; CREATE SCHEMA bla; -- We test the subcommand feature with the other repset_table tests SELECT pgl_ddl_deploy.undeploy(id) FROM pgl_ddl_deploy.set_configs; CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('test_ddl_only'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, ddl_only_replication) VALUES ('test_ddl_only','^super.*',false); -- It is now permitted to have multiple set_configs for same set_name if using ddl_only_replication INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, ddl_only_replication) VALUES ('test_ddl_only','^duper.*',true); SET ROLE postgres; SELECT pgl_ddl_deploy.deploy('test_ddl_only'); -- The difference here is that the latter table is under ddl_only_replication SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA super; CREATE TABLE super.man(id serial primary key); CREATE SCHEMA duper; CREATE TABLE duper.man(id serial primary key); -- Now assume we just want to replicate structure going forward ONLY ALTER TABLE super.man ADD COLUMN foo text; ALTER TABLE duper.man ADD COLUMN foo text; -- No cascade required for second drop because it was not added to replication DROP TABLE super.man CASCADE; DROP TABLE duper.man; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.ddl_only_replication FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; pgl_ddl_deploy-2.2.1/sql/24_sub_retries.sql000066400000000000000000000150611453171545300206630ustar00rootroot00000000000000--****NOTE*** this file drops the whole extension and all previous test setup. --If adding new tests, it is best to keep this file as the last test before cleanup. SET client_min_messages = warning; SELECT pubnames, message_type, regexp_replace(regexp_replace(regexp_replace(message::text, 'p_pid := (\d+)', 'p_pid := ?'), 'p_provider_name := (NULL|''\w+'')', 'p_provider_name := ?'), 'p_driver := (''\w+'')', 'p_driver := ?') as message FROM all_queues() WHERE NOT message::text LIKE '%notify_subscription_refresh%' ORDER BY queued_at; DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 AND NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN SELECT COUNT(1) INTO v_ct FROM all_queues() WHERE message::text LIKE '%notify_subscription_refresh%'; IF v_ct != 79 THEN RAISE EXCEPTION '%', v_ct; END IF; END IF; END$$; --Some day, we should regress with multiple databases. There are examples of this in pglogical code base --For now, we will mock the subscriber behavior, which is less than ideal, because it misses testing execution --on subscriber DROP OWNED BY test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy_nopriv; DROP EXTENSION pgl_ddl_deploy CASCADE; CREATE EXTENSION pgl_ddl_deploy; SET SESSION_REPLICATION_ROLE TO REPLICA; --To ensure testing subscriber behavior CREATE ROLE test_pgl_ddl_deploy; GRANT CREATE ON DATABASE contrib_regression TO test_pgl_ddl_deploy; SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname = 'test_pgl_ddl_deploy'; SET ROLE test_pgl_ddl_deploy; --Mock subscriber_log insert which should take place on subscriber error when option enabled INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 100, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW joy AS SELECT * FROM joyous', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous;', FALSE, 'relation "joyous" does not exist'); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; CREATE TABLE joyous (id int); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; --Now let's do 2 INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 101, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW happy AS SELECT * FROM happier;', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier;', FALSE, 'relation "happier" does not exist'); INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 102, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW glee AS SELECT * FROM gleeful;', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful;', FALSE, 'relation "gleeful" does not exist'); --The first fails and the second therefore is not attempted SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); --Both fail if we try each separately SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; --One succeeds, one fails CREATE TABLE happier (id int); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); --One fails SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; --Succeed with new id CREATE TABLE gleeful (id int); SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; --Nothing SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; DROP TABLE joyous CASCADE; DROP TABLE happier CASCADE; DROP TABLE gleeful CASCADE; pgl_ddl_deploy-2.2.1/sql/25_1_5_features.sql000066400000000000000000000214071453171545300206210ustar00rootroot00000000000000-- Suppress pid-specific warning messages SET client_min_messages TO error; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test1','.*',true, true); -- It's generally good to use queue_subscriber_failures with include_everything, so a bogus grant won't break replication on subscriber INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_everything, queue_subscriber_failures, create_tags) VALUES ('test1',true, true, '{GRANT,REVOKE}'); SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; DISCARD TEMP; SET search_path TO public; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key, bla int); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; GRANT SELECT ON foo TO PUBLIC; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; INSERT INTO foo (bla) VALUES (1),(2),(3); REVOKE INSERT ON foo FROM PUBLIC; DROP TABLE foo CASCADE; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; SELECT * FROM pgl_ddl_deploy.unhandled; SELECT * FROM pgl_ddl_deploy.exceptions; /***** Test cancel and terminate blocker functionality *****/ SET ROLE postgres; UPDATE pgl_ddl_deploy.set_configs SET lock_safe_deployment = FALSE, signal_blocking_subscriber_sessions = 'cancel'; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key, bla int); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; GRANT SELECT ON foo TO PUBLIC; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; INSERT INTO foo (bla) VALUES (1),(2),(3); REVOKE INSERT ON foo FROM PUBLIC; DROP TABLE foo CASCADE; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; SELECT * FROM pgl_ddl_deploy.unhandled; SELECT * FROM pgl_ddl_deploy.exceptions; CREATE TABLE public.foo(id serial primary key, bla int); CREATE TABLE public.foo2 () INHERITS (public.foo); CREATE TABLE public.bar(id serial primary key, bla int); \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('cancel','public','foo'); \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('terminate','public','foo'); \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & -- This process should not be killed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; INSERT INTO public.bar (bla) VALUES (1); SELECT pg_sleep(2); COMMIT;" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ADD COLUMN bar text;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ADD COLUMN bar text; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'cancel', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); TABLE public.foo; -- Now two processes to be killed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); -- This process will wait for the one above - but we want it to fail regardless of which gets killed first -- Avoid it firing our event triggers by using session_replication_role = replica \! PGOPTIONS='--client-min-messages=warning --session-replication-role=replica' psql -d contrib_regression -c "BEGIN; ALTER TABLE public.foo DROP COLUMN bar; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(2); SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ADD COLUMN super text;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ADD COLUMN super text; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'terminate', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); TABLE public.foo; /**** Try cancel_then_terminate, which should first try to cancel ****/ -- This process should be killed \! echo "BEGIN; SELECT * FROM public.foo;\n\! sleep 15" | psql contrib_regression > /dev/null 2>&1 & -- This process should not be killed \! psql contrib_regression -c "BEGIN; INSERT INTO public.bar (bla) VALUES (1); SELECT pg_sleep(5); COMMIT;" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ALTER COLUMN bar SET NOT NULL;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ALTER COLUMN bar SET NOT NULL; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'cancel_then_terminate', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); TABLE public.foo; /*** TEST INHERITANCE AND PARTITIONING ***/ -- Same workflow as above, but instead select from child, alter parent \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo2; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('terminate','public','foo'); /*** With <=1.5, it showed this. But it should kill the process. signal | successful | state | query | reported | pg_sleep --------+------------+-------+-------+----------+---------- (0 rows) ***/ DROP TABLE public.foo CASCADE; TABLE bar; DROP TABLE public.bar CASCADE; SELECT signal, successful, state, query, reported FROM pgl_ddl_deploy.killed_blockers ORDER BY signal, query; SELECT pg_sleep(1); -- Should be zero - everything was killed SELECT COUNT(1) FROM pg_stat_activity WHERE usename = session_user AND NOT pid = pg_backend_pid() AND query LIKE '%public.foo%'; pgl_ddl_deploy-2.2.1/sql/26_new_setup.sql000066400000000000000000000051771453171545300203570ustar00rootroot00000000000000--****NOTE*** this file drops the whole extension and all previous test setup. --If adding new tests, it is best to keep this file as the last test before cleanup. SET client_min_messages = warning; --Some day, we should regress with multiple databases. There are examples of this in pglogical code base --For now, we will mock the subscriber behavior, which is less than ideal, because it misses testing execution --on subscriber DROP EXTENSION pgl_ddl_deploy CASCADE; -- This test has been rewritten and presently exists for historical reasons and to maintain configuration CREATE EXTENSION pgl_ddl_deploy; --These are the same sets as in the new_set_behavior.sql INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_2', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; --One include_schema_regex one that should be unchanged CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('testspecial'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('testspecial','^special$',true, true); SELECT pgl_ddl_deploy.deploy('testspecial'); --These kinds of repsets will not replicate CREATE events, only ALTER TABLE, so deploy after CREATE --We assume schema will be copied to subscriber separately CREATE SCHEMA special; CREATE TABLE special.foo (id serial primary key, foo text, bar text); CREATE TABLE special.bar (id serial primary key, super text, man text); SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.foo'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_1'; SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.bar'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_2'; --Deploy by set_name SELECT pgl_ddl_deploy.deploy('my_special_tables_1'); SELECT pgl_ddl_deploy.deploy('my_special_tables_2'); pgl_ddl_deploy-2.2.1/sql/27_raise_message.sql000066400000000000000000000012751453171545300211510ustar00rootroot00000000000000SET client_min_messages TO WARNING; ALTER EXTENSION pgl_ddl_deploy UPDATE; -- Simple example SELECT pgl_ddl_deploy.raise_message('WARNING', 'foo'); -- Test case that needs % escapes SELECT pgl_ddl_deploy.raise_message('WARNING', $$SELECT foo FROM bar WHERE baz LIKE 'foo%'$$); /*** Failing message on 1.5 read: ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 3 SQL statement " DO $block$ BEGIN RAISE WARNING $pgl_ddl_deploy_msg$SELECT foo FROM bar WHERE baz LIKE 'foo%'$pgl_ddl_deploy_msg$; END$block$; " PL/pgSQL function pgl_ddl_deploy.raise_message(text,text) line 4 at EXECUTE ***/ SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/28_1_6_features.sql000066400000000000000000000035541453171545300206300ustar00rootroot00000000000000-- Configure this to only replicate functions or views -- This test is to ensure the config does NOT auto-add tables to replication (bug with <=1.5) UPDATE pgl_ddl_deploy.set_configs SET create_tags = '{"CREATE FUNCTION","ALTER FUNCTION","CREATE VIEW","ALTER VIEW"}' , drop_tags = '{"DROP FUNCTION","DROP VIEW"}' WHERE set_name = 'testspecial'; SELECT pgl_ddl_deploy.deploy('testspecial'); CREATE TEMP VIEW tables_in_replication AS SELECT COUNT(1) FROM pgl_ddl_deploy.rep_set_table_wrapper() t WHERE t.name = 'testspecial' AND NOT relid::REGCLASS::TEXT = 'pgl_ddl_deploy.queue'; TABLE tables_in_replication; CREATE TABLE special.do_not_replicate_me(id int primary key); TABLE tables_in_replication; -- In <=1.5, this would have hit the code path to add new tables to replication, even though -- the set is configured not to replicate CREATE TABLE events CREATE FUNCTION special.do_replicate_me() RETURNS INT AS 'SELECT 1' LANGUAGE SQL; -- This SHOULD show the same as above, but showed 1 more table in <=1.5 TABLE tables_in_replication; -- Test to ensure we are only setting these defaults (trigger set_tag_defaults) on INSERT UPDATE pgl_ddl_deploy.set_configs SET drop_tags = NULL WHERE set_name = 'testspecial' RETURNING drop_tags; /* In <= 1.5, returned this: drop_tags -------------------------------------------------------------------------------------- {"DROP SCHEMA","DROP TABLE","DROP FUNCTION","DROP TYPE","DROP VIEW","DROP SEQUENCE"} (1 row) */ SET client_min_messages TO warning; DROP OWNED BY test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy; DROP ROLE unpriv; DROP EXTENSION pgl_ddl_deploy CASCADE; DROP EXTENSION IF EXISTS pglogical CASCADE; DROP SCHEMA IF EXISTS pglogical CASCADE; DROP TABLE IF EXISTS tmp_objs; DROP SCHEMA IF EXISTS special CASCADE; DROP SCHEMA IF EXISTS bla CASCADE; DROP SCHEMA IF EXISTS pgl_ddl_deploy CASCADE; pgl_ddl_deploy-2.2.1/sql/29_create_ext.sql000066400000000000000000000016351453171545300204670ustar00rootroot00000000000000-- Allow running regression suite with upgrade paths \set v `echo ${FROMVERSION:-2.2}` SET client_min_messages = warning; CREATE TEMP TABLE v AS SELECT :'v'::TEXT AS num; DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 AND (SELECT num FROM v) != ALL('{1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7}'::text[]) THEN RAISE LOG '%', 'USING NATIVE'; ELSE CREATE EXTENSION pglogical; END IF; END$$; DROP TABLE v; CREATE EXTENSION pgl_ddl_deploy VERSION :'v'; CREATE FUNCTION set_driver() RETURNS VOID AS $BODY$ BEGIN IF current_setting('server_version_num')::INT >= 100000 AND (SELECT extversion::numeric FROM pg_extension WHERE extname = 'pgl_ddl_deploy') >= 2.0 THEN ALTER TABLE pgl_ddl_deploy.set_configs ALTER COLUMN driver SET DEFAULT 'native'::pgl_ddl_deploy.driver; UPDATE pgl_ddl_deploy.set_configs SET driver = 'native'::pgl_ddl_deploy.driver; END IF; END; $BODY$ LANGUAGE plpgsql; SELECT set_driver(); pgl_ddl_deploy-2.2.1/sql/30_setup.sql000066400000000000000000000054721453171545300174770ustar00rootroot00000000000000DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION test1; CREATE PUBLICATION test2; CREATE PUBLICATION test3; CREATE PUBLICATION test4; CREATE PUBLICATION test5; CREATE PUBLICATION test6; CREATE PUBLICATION test7; CREATE PUBLICATION test8;$sql$; ELSE CREATE TEMP TABLE foonode AS SELECT pglogical.create_node('test','host=localhost'); DROP TABLE foonode; CREATE TEMP TABLE repsets AS 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 FROM sets s; DROP TABLE repsets; END IF; END$$; CREATE ROLE test_pgl_ddl_deploy LOGIN; GRANT CREATE ON DATABASE contrib_regression TO test_pgl_ddl_deploy; SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname = 'test_pgl_ddl_deploy'; SET ROLE test_pgl_ddl_deploy; CREATE FUNCTION check_rep_tables() RETURNS TABLE (set_name TEXT, table_name TEXT) AS $BODY$ BEGIN -- Handle change from view to function rep_set_table_wrapper IF (SELECT extversion FROM pg_extension WHERE extname = 'pgl_ddl_deploy') = ANY('{1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7}'::text[]) THEN RETURN QUERY EXECUTE $$ SELECT set_name::TEXT, set_reloid::TEXT AS table_name FROM pgl_ddl_deploy.rep_set_table_wrapper rsr INNER JOIN pglogical.replication_set rs USING (set_id) ORDER BY set_name::TEXT, set_reloid::TEXT;$$; ELSE RETURN QUERY EXECUTE $$ SELECT name::TEXT AS set_name, relid::regclass::TEXT AS table_name FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE relid::regclass::TEXT <> 'pgl_ddl_deploy.queue' ORDER BY name::TEXT, relid::TEXT;$$; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION all_queues() RETURNS TABLE (queued_at timestamp with time zone, role name, pubnames text[], message_type "char", -- we use json here to provide test output consistency whether native or pglogical message json) AS $BODY$ BEGIN IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN RETURN QUERY EXECUTE $$ SELECT queued_at, role, replication_sets AS pubnames, message_type, message FROM pglogical.queue UNION ALL SELECT queued_at, role, pubnames, message_type, to_json(message) FROM pgl_ddl_deploy.queue;$$; ELSE RETURN QUERY EXECUTE $$ SELECT queued_at, role, pubnames, message_type, to_json(message) AS message FROM pgl_ddl_deploy.queue; $$; END IF; END; $BODY$ LANGUAGE plpgsql; CREATE FUNCTION verify_count(ct int, expected int) RETURNS BOOLEAN AS $BODY$ BEGIN RAISE LOG 'ct: %', ct; IF ct != expected THEN RAISE EXCEPTION 'Count % does not match expected count of %', ct, expected; END IF; RETURN TRUE; END$BODY$ LANGUAGE plpgsql; pgl_ddl_deploy-2.2.1/sql/31_add_configs.sql000066400000000000000000000026261453171545300205760ustar00rootroot00000000000000INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test1','.*',true, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test2','.*',true, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test3','.*',false, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test4','.*',false, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test5','^foo.*',true, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test6','^foo.*',true, false); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test7','^foo.*',false, true); INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test8','^foo.*',false, false); --Ensure regex must be valid INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test9','^foo.*((',false, false); pgl_ddl_deploy-2.2.1/sql/32_deploy_update.sql000066400000000000000000000022461453171545300211730ustar00rootroot00000000000000--This will show different warnings depending on if we are actually updating to new version or not SET client_min_messages = error; ALTER EXTENSION pgl_ddl_deploy UPDATE; SELECT set_driver(); SELECT pgl_ddl_deploy.deploy('test1'); DO $$ DECLARE v_rec RECORD; BEGIN FOR v_rec IN SELECT name FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name LIKE 'test%' AND name <> 'test1' ORDER BY name LOOP PERFORM pgl_ddl_deploy.deploy(v_rec.name); END LOOP; END$$; --Now that we are on highest version, ensure WARNING shows DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION testtemp;$sql$; ELSE CREATE TEMP TABLE repset AS SELECT pglogical.create_replication_set (set_name:='testtemp' ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE); DROP TABLE repset; END IF; END$$; SET client_min_messages = warning; BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (id, set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES (999, 'testtemp','.*',true, true); CREATE TABLE break(id serial primary key); SELECT pgl_ddl_deploy.deploy('testtemp'); ROLLBACK; pgl_ddl_deploy-2.2.1/sql/33_allowed.sql000066400000000000000000000074601453171545300177700ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; SET client_min_messages TO warning; /*** In default schema **/ CREATE TABLE foo(id serial primary key); SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER TABLE foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; INSERT INTO foo (bla) VALUES (1),(2),(3); DROP TABLE foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE FUNCTION foo() RETURNS INT AS $BODY$ SELECT 1; $BODY$ LANGUAGE SQL STABLE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER FUNCTION foo() OWNER TO current_user; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP FUNCTION foo(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE VIEW fooview AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER VIEW fooview RENAME TO barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP VIEW barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE SEQUENCE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER SEQUENCE foo RESTART; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP SEQUENCE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE SCHEMA foobar; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE TABLE foobar.foo(id serial primary key); SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER TABLE foobar.foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; INSERT INTO foobar.foo (bla) VALUES (1),(2),(3); DROP TABLE foobar.foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE FUNCTION foobar.foo() RETURNS INT AS $BODY$ SELECT 1; $BODY$ LANGUAGE SQL STABLE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER FUNCTION foobar.foo() OWNER TO current_user; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP FUNCTION foobar.foo(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE VIEW foobar.fooview AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER VIEW foobar.fooview RENAME TO barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP VIEW foobar.barview; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE SEQUENCE foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER SEQUENCE foobar.foo RESTART; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP SEQUENCE foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP SCHEMA foobar CASCADE; SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT * FROM pgl_ddl_deploy.unhandled; SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/34_multi.sql000066400000000000000000000063461453171545300174760ustar00rootroot00000000000000SET log_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA foobar; --This should never be allowed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); INSERT INTO foo (id) VALUES (1),(2),(3); DROP TABLE foo;" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); INSERT INTO foobar.foo (id) VALUES (1),(2),(3); DROP TABLE foobar.foo;" SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foo(id int primary key); COMMIT;" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; CREATE TABLE foobar.foo(id int primary key); COMMIT;" SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; --Run all commands through cli to avoid permissions issues \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo CASCADE;" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foobar.foo CASCADE;" --This should be allowed by some configurations, and others not \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key); DROP TABLE foo CASCADE;" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key); DROP TABLE foobar.foo CASCADE;" SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foo(id int primary key);" \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "CREATE TABLE foobar.foo(id int primary key);" --This should be allowed by some but not others \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "DROP TABLE foo, foobar.foo CASCADE;" SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; --Resolutions SELECT pgl_ddl_deploy.resolve_unhandled(id, 'DBA superhero deployed it manually on the subscribers!') FROM pgl_ddl_deploy.unhandled; --Test with no rows and a dummy row SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; BEGIN; INSERT INTO pgl_ddl_deploy.exceptions (set_name) VALUES ('test1'); SELECT pgl_ddl_deploy.resolve_exception(id, 'Mystery solved') FROM pgl_ddl_deploy.exceptions; ROLLBACK; SELECT resolved, resolved_notes, set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/35_edges.sql000066400000000000000000000007231453171545300174250ustar00rootroot00000000000000SET client_min_messages TO warning; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foobar.foo (id SERIAL PRIMARY KEY); CREATE TABLE foo (id SERIAL PRIMARY KEY); --This is an edge case that currently can't be dealt with well with filtered replication. ALTER TABLE foobar.foo ADD COLUMN foo_id INT REFERENCES foo(id); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; DROP TABLE foobar.foo CASCADE; DROP TABLE foo CASCADE; pgl_ddl_deploy-2.2.1/sql/36_ignored.sql000066400000000000000000000005271453171545300177700ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; CREATE TEMP TABLE foo(id SERIAL PRIMARY KEY); ALTER TABLE foo ADD COLUMN bla TEXT; DROP TABLE foo; SELECT 1 AS myfield INTO TEMP foo; DROP TABLE foo; CREATE TEMP TABLE foo AS SELECT 1 AS myfield; DROP TABLE foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; pgl_ddl_deploy-2.2.1/sql/37_unsupported.sql000066400000000000000000000012211453171545300207220ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo AS SELECT 1 AS myfield; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; SELECT 1 AS myfield INTO foobar.foo; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; SELECT set_name, ddl_sql_raw, command_tag, reason FROM pgl_ddl_deploy.unhandled ORDER BY id DESC LIMIT 10; DROP TABLE foo; DROP TABLE foobar.foo; SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/38_no_create_user.sql000066400000000000000000000003251453171545300213340ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE CREATE ROLE test_pgl_ddl_deploy_nopriv; SET ROLE test_pgl_ddl_deploy_nopriv; CREATE TEMP TABLE bla (id serial primary key); DROP TABLE bla; RESET ROLE; pgl_ddl_deploy-2.2.1/sql/39_override.sql000066400000000000000000000005051453171545300201570ustar00rootroot00000000000000SET client_min_messages TO warning; \set VERBOSITY TERSE SET SESSION_REPLICATION_ROLE TO REPLICA; CREATE TABLE i_want_to_ignore_evts (id serial primary key); DROP TABLE i_want_to_ignore_evts; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; RESET SESSION_REPLICATION_ROLE; pgl_ddl_deploy-2.2.1/sql/40_sql_command_tags.sql000066400000000000000000000004211453171545300216400ustar00rootroot00000000000000SELECT pgl_ddl_deploy.sql_command_tags(NULL); SELECT pgl_ddl_deploy.sql_command_tags(''); SELECT pgl_ddl_deploy.sql_command_tags('CREATE EXTENSON foo;'); SELECT pgl_ddl_deploy.sql_command_tags('CREATE TABLE foo(); ALTER TABLE foo ADD COLUMN bar text; DROP TABLE foo;'); pgl_ddl_deploy-2.2.1/sql/41_transaction.sql000066400000000000000000000021461453171545300206610ustar00rootroot00000000000000SET ROLE test_pgl_ddl_deploy; SET client_min_messages TO warning; BEGIN; /*** In default schema **/ CREATE TABLE foo(id serial primary key); SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER TABLE foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; INSERT INTO foo (bla) VALUES (1),(2),(3); DROP TABLE foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; CREATE TABLE foobar.foo(id serial primary key); SELECT * FROM check_rep_tables(); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; ALTER TABLE foobar.foo ADD COLUMN bla TEXT; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; INSERT INTO foobar.foo (bla) VALUES (1),(2),(3); DROP TABLE foobar.foo CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; COMMIT; SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/43_new_set_behavior.sql000066400000000000000000000074431453171545300216660ustar00rootroot00000000000000SET client_min_messages = warning; \set VERBOSITY terse --This should fail due to overlapping tags INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'test1', '.*', TRUE, TRUE, FALSE, '{"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"}', '{"DROP VIEW","DROP FUNCTION"}'; --But if we drop these tags from test1, it should work UPDATE pgl_ddl_deploy.set_configs SET create_tags = '{ALTER TABLE,CREATE SEQUENCE,ALTER SEQUENCE,CREATE SCHEMA,CREATE TABLE,CREATE TYPE,ALTER TYPE}', drop_tags = '{DROP SCHEMA,DROP TABLE,DROP TYPE,DROP SEQUENCE}' WHERE set_name = 'test1'; --Now this set will only handle these tags INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'test1', '.*', TRUE, TRUE, FALSE, '{"CREATE VIEW","ALTER VIEW","CREATE FUNCTION","ALTER FUNCTION"}', '{"DROP VIEW","DROP FUNCTION"}'; --include_only_repset_tables DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION my_special_tables_1; CREATE PUBLICATION my_special_tables_2;$sql$; ELSE CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('my_special_tables_1'::TEXT), ('my_special_tables_2'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; END IF; END$$; --Only ALTER TABLE makes sense (and is allowed) with include_only_repset_tables. So this should fail INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"CREATE TABLE"}'; --This is OK INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'temp_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; DELETE FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; --This also should fail - no DROP tags at all allowed INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', '{"DROP TABLE"}'; --These both are OK INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_2', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; --Check we get the defaults we want from the trigger BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex) VALUES ('temp_1', '.*'); SELECT create_tags, drop_tags FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; ROLLBACK; BEGIN; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_only_repset_tables) VALUES ('temp_1', TRUE); SELECT create_tags, drop_tags FROM pgl_ddl_deploy.set_configs WHERE set_name = 'temp_1'; ROLLBACK; --Now deploy again separately --By set_name: SELECT pgl_ddl_deploy.deploy('test1'); --By set_config_id SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; pgl_ddl_deploy-2.2.1/sql/44_multi_set_tags.sql000066400000000000000000000021251453171545300213570ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA viewer; --Should be handled by separate set_config CREATE TABLE viewer.foo(id int primary key); --Should be handled by separate set_config CREATE VIEW viewer.vw_foo AS SELECT * FROM viewer.foo; SELECT c.create_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name = 'test1' ORDER BY e.id DESC LIMIT 4; DROP VIEW viewer.vw_foo; DROP TABLE viewer.foo CASCADE; DROP SCHEMA viewer; SELECT c.drop_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name = 'test1' ORDER BY e.id DESC LIMIT 4; SELECT * FROM pgl_ddl_deploy.exceptions; DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN SELECT COUNT(1) INTO v_ct FROM pg_publication_tables WHERE schemaname = 'pgl_ddl_deploy' AND tablename = 'queue'; RAISE LOG 'v_ct: %', v_ct; PERFORM verify_count(v_ct, 8); END IF; END$$; pgl_ddl_deploy-2.2.1/sql/45_include_only_repset_tables_1.sql000066400000000000000000000006061453171545300241570ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; --These kinds of repsets will not replicate CREATE events, only ALTER TABLE, so deploy after CREATE --We assume schema will be copied to subscriber separately CREATE SCHEMA special; CREATE TABLE special.foo (id serial primary key, foo text, bar text); CREATE TABLE special.bar (id serial primary key, super text, man text); pgl_ddl_deploy-2.2.1/sql/46_include_only_repset_tables_2.sql000066400000000000000000000021671453171545300241650ustar00rootroot00000000000000SET client_min_messages = warning; SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.foo'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_1'; SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.bar'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_2'; --Deploy by set_name SELECT pgl_ddl_deploy.deploy('my_special_tables_1'); SELECT pgl_ddl_deploy.deploy('my_special_tables_2'); --Ensure these kinds of configs only have 'create' event triggers SELECT COUNT(1) FROM pg_event_trigger evt INNER JOIN pgl_ddl_deploy.event_trigger_schema ets ON evt.evtname IN(auto_replication_unsupported_trigger_name, ets.auto_replication_drop_trigger_name, ets.auto_replication_create_trigger_name) WHERE include_only_repset_tables; --Deploy by id SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'my_special_tables_1'; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'my_special_tables_2'; pgl_ddl_deploy-2.2.1/sql/47_include_only_repset_tables_3.sql000066400000000000000000000020341453171545300241600ustar00rootroot00000000000000SET client_min_messages = warning; SET ROLE test_pgl_ddl_deploy; ALTER TABLE special.foo ADD COLUMN happy TEXT; ALTER TABLE special.bar ADD COLUMN happier TEXT; SELECT c.create_tags, c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; --Test renaming which was missing in 1.2 ALTER TABLE special.foo RENAME COLUMN happy to happyz; ALTER TABLE special.foo ADD CONSTRAINT bla CHECK (true); ALTER TABLE special.foo RENAME CONSTRAINT bla to blaz; ALTER TABLE special.foo RENAME COLUMN id TO id_2; ALTER TABLE special.bar RENAME COLUMN happier TO happierz; ALTER TABLE special.bar RENAME COLUMN id TO id_3; ALTER TABLE special.foo RENAME TO fooz; ALTER TABLE special.bar RENAME TO barz; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 20; pgl_ddl_deploy-2.2.1/sql/48_include_only_repset_tables_4.sql000066400000000000000000000031071453171545300241640ustar00rootroot00000000000000SET client_min_messages = warning; CREATE OR REPLACE FUNCTION noop() RETURNS TRIGGER AS $BODY$ BEGIN RETURN NULL; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER noop BEFORE DELETE ON special.fooz FOR EACH ROW EXECUTE PROCEDURE noop(); ALTER TABLE special.fooz DISABLE TRIGGER noop; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; -- Test new subcommand functionality UPDATE pgl_ddl_deploy.set_configs SET exclude_alter_table_subcommands = pgl_ddl_deploy.common_exclude_alter_table_subcommands() WHERE include_only_repset_tables; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE include_only_repset_tables; SET client_min_messages = log; -- This should be ignored ALTER TABLE special.fooz ENABLE TRIGGER noop; -- This contains a tag we want to ignore but we can't separate out the parts - see the warning message ALTER TABLE special.barz ADD COLUMN foo_id INT REFERENCES special.fooz (id_2); ALTER TABLE special.fooz ADD COLUMN bar_id INT; -- This one should be ignored as well ALTER TABLE special.fooz ADD CONSTRAINT coolness FOREIGN KEY (bar_id) REFERENCES special.barz (id_3); SELECT c.set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id WHERE c.set_name LIKE 'my_special_tables%' ORDER BY e.id DESC LIMIT 10; SET client_min_messages = warning; DROP TABLE special.fooz CASCADE; DROP TABLE special.barz CASCADE; DROP SCHEMA special; pgl_ddl_deploy-2.2.1/sql/49_unprivileged_users.sql000066400000000000000000000001411453171545300222530ustar00rootroot00000000000000CREATE ROLE unpriv; SET ROLE unpriv; CREATE TEMP TABLE foo(); ALTER TABLE foo ADD COLUMN id INT; pgl_ddl_deploy-2.2.1/sql/50_is_deployed.sql000066400000000000000000000015701453171545300206340ustar00rootroot00000000000000SET client_min_messages TO warning; --Test what is_deployed shows (introduced in 1.3) SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; SELECT pgl_ddl_deploy.undeploy(id) FROM pgl_ddl_deploy.set_configs; SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; --Nothing should replicate this CREATE TABLE foobar (id serial primary key); DROP TABLE foobar; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; --Re-deploy and check again what shows as deployed SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs; SELECT set_name, is_deployed FROM pgl_ddl_deploy.event_trigger_schema ORDER BY id; CREATE TABLE foobar (id serial primary key); DROP TABLE foobar CASCADE; SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; pgl_ddl_deploy-2.2.1/sql/51_1_4_features.sql000066400000000000000000000036771453171545300206300ustar00rootroot00000000000000SET client_min_messages = warning; -- We need to eventually test this on a real subscriber SET search_path TO ''; CREATE SCHEMA bla; -- We test the subcommand feature with the other repset_table tests SELECT pgl_ddl_deploy.undeploy(id) FROM pgl_ddl_deploy.set_configs; DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION test_ddl_only;$sql$; ELSE CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('test_ddl_only'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; END IF; END$$; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, ddl_only_replication) VALUES ('test_ddl_only','^super.*',false); -- It is now permitted to have multiple set_configs for same set_name if using ddl_only_replication INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, ddl_only_replication) VALUES ('test_ddl_only','^duper.*',true); SET ROLE postgres; SELECT pgl_ddl_deploy.deploy('test_ddl_only'); -- The difference here is that the latter table is under ddl_only_replication SET ROLE test_pgl_ddl_deploy; CREATE SCHEMA super; CREATE TABLE super.man(id serial primary key); CREATE SCHEMA duper; CREATE TABLE duper.man(id serial primary key); -- Now assume we just want to replicate structure going forward ONLY ALTER TABLE super.man ADD COLUMN foo text; ALTER TABLE duper.man ADD COLUMN foo text; -- No cascade required for second drop because it was not added to replication DROP TABLE super.man CASCADE; DROP TABLE duper.man; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.ddl_only_replication FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; pgl_ddl_deploy-2.2.1/sql/52_sub_retries.sql000066400000000000000000000151061453171545300206640ustar00rootroot00000000000000--****NOTE*** this file drops the whole extension and all previous test setup. --If adding new tests, it is best to keep this file as the last test before cleanup. SET client_min_messages = warning; SELECT pubnames, message_type, regexp_replace(regexp_replace(regexp_replace(message::text, 'p_pid := (\d+)', 'p_pid := ?'), 'p_provider_name := (NULL|''\w+'')', 'p_provider_name := ?'), 'p_driver := (''\w+'')', 'p_driver := ?') as message FROM all_queues() WHERE NOT message::text LIKE '%notify_subscription_refresh%' ORDER BY queued_at; DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 AND NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pglogical') THEN SELECT COUNT(1) INTO v_ct FROM all_queues() WHERE message::text LIKE '%notify_subscription_refresh%'; IF v_ct != 79 THEN RAISE EXCEPTION '%', v_ct; END IF; END IF; END$$; --Some day, we should regress with multiple databases. There are examples of this in pglogical code base --For now, we will mock the subscriber behavior, which is less than ideal, because it misses testing execution --on subscriber DROP OWNED BY test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy_nopriv; DROP EXTENSION pgl_ddl_deploy CASCADE; CREATE EXTENSION pgl_ddl_deploy; SELECT set_driver(); SET SESSION_REPLICATION_ROLE TO REPLICA; --To ensure testing subscriber behavior CREATE ROLE test_pgl_ddl_deploy; GRANT CREATE ON DATABASE contrib_regression TO test_pgl_ddl_deploy; SELECT pgl_ddl_deploy.add_role(oid) FROM pg_roles WHERE rolname = 'test_pgl_ddl_deploy'; SET ROLE test_pgl_ddl_deploy; --Mock subscriber_log insert which should take place on subscriber error when option enabled INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 100, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW joy AS SELECT * FROM joyous', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW joy AS SELECT * FROM joyous;', FALSE, 'relation "joyous" does not exist'); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; CREATE TABLE joyous (id int); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; --Now let's do 2 INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 101, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW happy AS SELECT * FROM happier;', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW happy AS SELECT * FROM happier;', FALSE, 'relation "happier" does not exist'); INSERT INTO pgl_ddl_deploy.subscriber_logs (set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, subscriber_pid, executed_at, ddl_sql, full_ddl_sql, succeeded, error_message) VALUES ('foo', 102, 'awesome', 1, 'test_pgl_ddl_deploy', pg_backend_pid(), current_timestamp, 'CREATE VIEW glee AS SELECT * FROM gleeful;', 'SET ROLE test_pgl_ddl_deploy; CREATE VIEW glee AS SELECT * FROM gleeful;', FALSE, 'relation "gleeful" does not exist'); --The first fails and the second therefore is not attempted SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); --Both fail if we try each separately SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; --One succeeds, one fails CREATE TABLE happier (id int); SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); --One fails SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; --Succeed with new id CREATE TABLE gleeful (id int); SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; --Nothing SELECT pgl_ddl_deploy.retry_subscriber_log(rq.id) FROM pgl_ddl_deploy.subscriber_logs rq INNER JOIN pgl_ddl_deploy.subscriber_logs rqo ON rqo.id = rq.origin_subscriber_log_id WHERE NOT rq.succeeded AND rq.next_subscriber_log_id IS NULL AND NOT rq.retrying ORDER BY rqo.executed_at ASC, rqo.origin_subscriber_log_id ASC; SELECT pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT id, set_name, provider_pid, provider_node_name, provider_set_config_id, executed_as_role, origin_subscriber_log_id, next_subscriber_log_id, ddl_sql, full_ddl_sql, succeeded, error_message FROM pgl_ddl_deploy.subscriber_logs ORDER BY id; DROP TABLE joyous CASCADE; DROP TABLE happier CASCADE; DROP TABLE gleeful CASCADE; pgl_ddl_deploy-2.2.1/sql/53_1_5_features.sql000066400000000000000000000214071453171545300206220ustar00rootroot00000000000000-- Suppress pid-specific warning messages SET client_min_messages TO error; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('test1','.*',true, true); -- It's generally good to use queue_subscriber_failures with include_everything, so a bogus grant won't break replication on subscriber INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_everything, queue_subscriber_failures, create_tags) VALUES ('test1',true, true, '{GRANT,REVOKE}'); SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; DISCARD TEMP; SET search_path TO public; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key, bla int); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; GRANT SELECT ON foo TO PUBLIC; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; INSERT INTO foo (bla) VALUES (1),(2),(3); REVOKE INSERT ON foo FROM PUBLIC; DROP TABLE foo CASCADE; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; SELECT * FROM pgl_ddl_deploy.unhandled; SELECT * FROM pgl_ddl_deploy.exceptions; /***** Test cancel and terminate blocker functionality *****/ SET ROLE postgres; UPDATE pgl_ddl_deploy.set_configs SET lock_safe_deployment = FALSE, signal_blocking_subscriber_sessions = 'cancel'; SELECT pgl_ddl_deploy.deploy(id) FROM pgl_ddl_deploy.set_configs WHERE set_name = 'test1'; SET ROLE test_pgl_ddl_deploy; CREATE TABLE foo(id serial primary key, bla int); SELECT set_name, ddl_sql_raw, ddl_sql_sent FROM pgl_ddl_deploy.events ORDER BY id DESC LIMIT 10; GRANT SELECT ON foo TO PUBLIC; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; INSERT INTO foo (bla) VALUES (1),(2),(3); REVOKE INSERT ON foo FROM PUBLIC; DROP TABLE foo CASCADE; SELECT c.set_name, ddl_sql_raw, ddl_sql_sent, c.include_everything FROM pgl_ddl_deploy.events e INNER JOIN pgl_ddl_deploy.set_configs c ON c.id = e.set_config_id ORDER BY e.id DESC LIMIT 10; SELECT * FROM pgl_ddl_deploy.unhandled; SELECT * FROM pgl_ddl_deploy.exceptions; CREATE TABLE public.foo(id serial primary key, bla int); CREATE TABLE public.foo2 () INHERITS (public.foo); CREATE TABLE public.bar(id serial primary key, bla int); \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('cancel','public','foo'); \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('terminate','public','foo'); \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & -- This process should not be killed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; INSERT INTO public.bar (bla) VALUES (1); SELECT pg_sleep(2); COMMIT;" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ADD COLUMN bar text;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ADD COLUMN bar text; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'cancel', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); TABLE public.foo; -- Now two processes to be killed \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); -- This process will wait for the one above - but we want it to fail regardless of which gets killed first -- Avoid it firing our event triggers by using session_replication_role = replica \! PGOPTIONS='--client-min-messages=warning --session-replication-role=replica' psql -d contrib_regression -c "BEGIN; ALTER TABLE public.foo DROP COLUMN bar; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(2); SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ADD COLUMN super text;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ADD COLUMN super text; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'terminate', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); TABLE public.foo; /**** Try cancel_then_terminate, which should first try to cancel ****/ -- This process should be killed \! echo "BEGIN; SELECT * FROM public.foo;\n\! sleep 15" | psql contrib_regression > /dev/null 2>&1 & -- This process should not be killed \! psql contrib_regression -c "BEGIN; INSERT INTO public.bar (bla) VALUES (1); SELECT pg_sleep(5); COMMIT;" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := 'test', p_set_name := ARRAY['test1'], p_nspname := 'public', p_relname := 'foo', p_ddl_sql_sent := $pgl_ddl_deploy_sql$ALTER TABLE public.foo ALTER COLUMN bar SET NOT NULL;$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO public; ALTER TABLE public.foo ALTER COLUMN bar SET NOT NULL; ; $pgl_ddl_deploy_sql$, p_pid := pg_backend_pid(), p_set_config_id := 1, p_queue_subscriber_failures := false, p_signal_blocking_subscriber_sessions := 'cancel_then_terminate', -- Lower lock_timeout to make this test run faster p_lock_timeout := 300, p_driver := (SELECT driver FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'test1'), -- This parameter is only marked TRUE for this function to be able to easily run on a provider for regression testing p_run_anywhere := TRUE ); TABLE public.foo; /*** TEST INHERITANCE AND PARTITIONING ***/ -- Same workflow as above, but instead select from child, alter parent \! PGOPTIONS='--client-min-messages=warning' psql -d contrib_regression -c "BEGIN; SELECT * FROM public.foo2; SELECT pg_sleep(30);" > /dev/null 2>&1 & SELECT pg_sleep(1); SELECT signal, successful, state, query, reported, pg_sleep(1) FROM pgl_ddl_deploy.kill_blockers('terminate','public','foo'); /*** With <=1.5, it showed this. But it should kill the process. signal | successful | state | query | reported | pg_sleep --------+------------+-------+-------+----------+---------- (0 rows) ***/ DROP TABLE public.foo CASCADE; TABLE bar; DROP TABLE public.bar CASCADE; SELECT signal, successful, state, query, reported FROM pgl_ddl_deploy.killed_blockers ORDER BY signal, query; SELECT pg_sleep(1); -- Should be zero - everything was killed SELECT COUNT(1) FROM pg_stat_activity WHERE usename = session_user AND NOT pid = pg_backend_pid() AND query LIKE '%public.foo%'; pgl_ddl_deploy-2.2.1/sql/54_new_setup.sql000066400000000000000000000054501453171545300203520ustar00rootroot00000000000000--****NOTE*** this file drops the whole extension and all previous test setup. --If adding new tests, it is best to keep this file as the last test before cleanup. SET client_min_messages = warning; --Some day, we should regress with multiple databases. There are examples of this in pglogical code base --For now, we will mock the subscriber behavior, which is less than ideal, because it misses testing execution --on subscriber DROP EXTENSION pgl_ddl_deploy CASCADE; -- This test has been rewritten and presently exists for historical reasons and to maintain configuration CREATE EXTENSION pgl_ddl_deploy; SELECT set_driver(); --These are the same sets as in the new_set_behavior.sql INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_1', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements, include_only_repset_tables, create_tags, drop_tags) SELECT 'my_special_tables_2', NULL, TRUE, TRUE, TRUE, '{"ALTER TABLE"}', NULL; --One include_schema_regex one that should be unchanged DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN EXECUTE $sql$ CREATE PUBLICATION testspecial; $sql$; ELSE CREATE TEMP TABLE repsets AS WITH new_sets (set_name) AS ( VALUES ('testspecial'::TEXT) ) SELECT pglogical.create_replication_set (set_name:=s.set_name ,replicate_insert:=TRUE ,replicate_update:=TRUE ,replicate_delete:=TRUE ,replicate_truncate:=TRUE) AS result FROM new_sets s WHERE NOT EXISTS ( SELECT 1 FROM pglogical.replication_set WHERE set_name = s.set_name); DROP TABLE repsets; END IF; END$$; INSERT INTO pgl_ddl_deploy.set_configs (set_name, include_schema_regex, lock_safe_deployment, allow_multi_statements) VALUES ('testspecial','^special$',true, true); SELECT pgl_ddl_deploy.deploy('testspecial'); --These kinds of repsets will not replicate CREATE events, only ALTER TABLE, so deploy after CREATE --We assume schema will be copied to subscriber separately CREATE SCHEMA special; CREATE TABLE special.foo (id serial primary key, foo text, bar text); CREATE TABLE special.bar (id serial primary key, super text, man text); SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.foo'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_1'; SELECT pgl_ddl_deploy.add_table_to_replication( p_driver:=driver ,p_set_name:=name ,p_relation:='special.bar'::REGCLASS) FROM pgl_ddl_deploy.rep_set_wrapper() WHERE name = 'my_special_tables_2'; --Deploy by set_name SELECT pgl_ddl_deploy.deploy('my_special_tables_1'); SELECT pgl_ddl_deploy.deploy('my_special_tables_2'); pgl_ddl_deploy-2.2.1/sql/55_raise_message.sql000066400000000000000000000012751453171545300211520ustar00rootroot00000000000000SET client_min_messages TO WARNING; ALTER EXTENSION pgl_ddl_deploy UPDATE; -- Simple example SELECT pgl_ddl_deploy.raise_message('WARNING', 'foo'); -- Test case that needs % escapes SELECT pgl_ddl_deploy.raise_message('WARNING', $$SELECT foo FROM bar WHERE baz LIKE 'foo%'$$); /*** Failing message on 1.5 read: ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 3 SQL statement " DO $block$ BEGIN RAISE WARNING $pgl_ddl_deploy_msg$SELECT foo FROM bar WHERE baz LIKE 'foo%'$pgl_ddl_deploy_msg$; END$block$; " PL/pgSQL function pgl_ddl_deploy.raise_message(text,text) line 4 at EXECUTE ***/ SELECT * FROM pgl_ddl_deploy.exceptions; pgl_ddl_deploy-2.2.1/sql/56_1_6_features.sql000066400000000000000000000035541453171545300206310ustar00rootroot00000000000000-- Configure this to only replicate functions or views -- This test is to ensure the config does NOT auto-add tables to replication (bug with <=1.5) UPDATE pgl_ddl_deploy.set_configs SET create_tags = '{"CREATE FUNCTION","ALTER FUNCTION","CREATE VIEW","ALTER VIEW"}' , drop_tags = '{"DROP FUNCTION","DROP VIEW"}' WHERE set_name = 'testspecial'; SELECT pgl_ddl_deploy.deploy('testspecial'); CREATE TEMP VIEW tables_in_replication AS SELECT COUNT(1) FROM pgl_ddl_deploy.rep_set_table_wrapper() t WHERE t.name = 'testspecial' AND NOT relid::REGCLASS::TEXT = 'pgl_ddl_deploy.queue'; TABLE tables_in_replication; CREATE TABLE special.do_not_replicate_me(id int primary key); TABLE tables_in_replication; -- In <=1.5, this would have hit the code path to add new tables to replication, even though -- the set is configured not to replicate CREATE TABLE events CREATE FUNCTION special.do_replicate_me() RETURNS INT AS 'SELECT 1' LANGUAGE SQL; -- This SHOULD show the same as above, but showed 1 more table in <=1.5 TABLE tables_in_replication; -- Test to ensure we are only setting these defaults (trigger set_tag_defaults) on INSERT UPDATE pgl_ddl_deploy.set_configs SET drop_tags = NULL WHERE set_name = 'testspecial' RETURNING drop_tags; /* In <= 1.5, returned this: drop_tags -------------------------------------------------------------------------------------- {"DROP SCHEMA","DROP TABLE","DROP FUNCTION","DROP TYPE","DROP VIEW","DROP SEQUENCE"} (1 row) */ SET client_min_messages TO warning; DROP OWNED BY test_pgl_ddl_deploy; DROP ROLE test_pgl_ddl_deploy; DROP ROLE unpriv; DROP EXTENSION pgl_ddl_deploy CASCADE; DROP EXTENSION IF EXISTS pglogical CASCADE; DROP SCHEMA IF EXISTS pglogical CASCADE; DROP TABLE IF EXISTS tmp_objs; DROP SCHEMA IF EXISTS special CASCADE; DROP SCHEMA IF EXISTS bla CASCADE; DROP SCHEMA IF EXISTS pgl_ddl_deploy CASCADE; pgl_ddl_deploy-2.2.1/sql/57_native_features.sql000066400000000000000000000046211453171545300215270ustar00rootroot00000000000000 SET client_min_messages = warning; DO $$ BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN SET session_replication_role TO replica; ELSE CREATE EXTENSION pglogical; END IF; END$$; CREATE EXTENSION pgl_ddl_deploy; CREATE OR REPLACE FUNCTION pgl_ddl_deploy.override() RETURNS BOOLEAN AS $BODY$ BEGIN RETURN TRUE; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; INSERT INTO pgl_ddl_deploy.queue (queued_at,role,pubnames,message_type,message) VALUES (now(),current_role,'{mock}'::TEXT[],pgl_ddl_deploy.queue_ddl_message_type(),'CREATE TABLE nativerox(id int)'); INSERT INTO pgl_ddl_deploy.queue (queued_at,role,pubnames,message_type,message) VALUES (now(),current_role,'{mock}'::TEXT[],pgl_ddl_deploy.queue_ddl_message_type(),'ALTER TABLE nativerox ADD COLUMN bar text;'); INSERT INTO pgl_ddl_deploy.queue (queued_at,role,pubnames,message_type,message) VALUES (now(),current_role,'{mock}'::TEXT[],pgl_ddl_deploy.queue_ddl_message_type(),$$SELECT pgl_ddl_deploy.notify_subscription_refresh('mock', true);$$); CREATE FUNCTION verify_count(ct int, expected int) RETURNS BOOLEAN AS $BODY$ BEGIN RAISE LOG 'ct: %', ct; IF ct != expected THEN RAISE EXCEPTION 'Count % does not match expected count of %', ct, expected; END IF; RETURN TRUE; END$BODY$ LANGUAGE plpgsql; DO $$ DECLARE v_ct INT; BEGIN IF current_setting('server_version_num')::INT >= 100000 THEN SELECT COUNT(1) INTO v_ct FROM information_schema.columns WHERE table_name = 'nativerox'; PERFORM verify_count(v_ct, 2); SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.subscriber_logs; PERFORM verify_count(v_ct, 1); PERFORM pgl_ddl_deploy.retry_all_subscriber_logs(); SELECT (SELECT COUNT(1) FROM pgl_ddl_deploy.subscriber_logs WHERE NOT succeeded) + (SELECT COUNT(1) FROM pgl_ddl_deploy.subscriber_logs WHERE error_message ~* 'No subscription to publication mock exists') INTO v_ct; PERFORM verify_count(v_ct, 3); -- test for duplicate avoidance with multiple subscriptions SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.queue; PERFORM verify_count(v_ct, 3); SET session_replication_role TO replica; INSERT INTO pgl_ddl_deploy.queue SELECT * FROM pgl_ddl_deploy.queue; SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.queue; PERFORM verify_count(v_ct, 3); RESET session_replication_role; ELSE SELECT COUNT(1) INTO v_ct FROM pgl_ddl_deploy.subscriber_logs; PERFORM verify_count(v_ct, 0); END IF; END$$; pgl_ddl_deploy-2.2.1/test_all_versions.sh000077500000000000000000000017241453171545300206070ustar00rootroot00000000000000#!/bin/bash set -eu orig_path=$PATH 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 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 } test_all_versions() { from_version="$1" cat << EOM *******************FROM VERSION $from_version****************** EOM make_and_test "11" $from_version make_and_test "12" $from_version make_and_test "13" $from_version make_and_test "14" $from_version make_and_test "15" $from_version make_and_test "16" $from_version } test_all_versions "2.2" test_all_versions "2.1" pgl_ddl_deploy-2.2.1/triggers/000077500000000000000000000000001453171545300163335ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/triggers/unique_tags.sql000066400000000000000000000002151453171545300213760ustar00rootroot00000000000000CREATE TRIGGER unique_tags AFTER INSERT OR UPDATE ON pgl_ddl_deploy.set_configs FOR EACH ROW EXECUTE PROCEDURE pgl_ddl_deploy.unique_tags(); pgl_ddl_deploy-2.2.1/views/000077500000000000000000000000001453171545300156425ustar00rootroot00000000000000pgl_ddl_deploy-2.2.1/views/event_trigger_schema.sql000066400000000000000000000650751453171545300225640ustar00rootroot00000000000000CREATE OR REPLACE VIEW pgl_ddl_deploy.event_trigger_schema AS WITH vars AS (SELECT sc.id, set_name, 'pgl_ddl_deploy.auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_function_name, 'pgl_ddl_deploy.auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_function_name, 'pgl_ddl_deploy.auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_function_name, 'auto_rep_ddl_create_'||sc.id::TEXT||'_'||set_name AS auto_replication_create_trigger_name, 'auto_rep_ddl_drop_'||sc.id::TEXT||'_'||set_name AS auto_replication_drop_trigger_name, 'auto_rep_ddl_unsupp_'||sc.id::TEXT||'_'||set_name AS auto_replication_unsupported_trigger_name, include_schema_regex, include_only_repset_tables, create_tags, drop_tags, ddl_only_replication, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, sc.driver, /**** These constants in DECLARE portion of all functions is identical and can be shared */ $BUILD$ c_search_path TEXT = (SELECT current_setting('search_path')); c_provider_name TEXT; --TODO: How do I decide which replication set we care about? v_pid INT = pg_backend_pid(); v_rec RECORD; v_ddl_sql_raw TEXT; v_ddl_sql_sent TEXT; v_full_ddl TEXT; v_sql_tags TEXT[]; v_cmd_rec RECORD; v_subcmd_rec RECORD; v_excluded_subcommands TEXT; v_contains_any_valid_subcommand INT; /***** We need to strip the DDL of: 1. Transaction begin and commit, which cannot run inside plpgsql *****/ v_ddl_strip_regex TEXT = '(begin\W*transaction\W*|begin\W*work\W*|begin\W*|commit\W*transaction\W*|commit\W*work\W*|commit\W*);'; v_txid BIGINT; v_ddl_length INT; v_sql TEXT; v_cmd_count INT; v_match_count INT; v_exclude_always_match_count INT; v_nspname TEXT; v_relname TEXT; v_error TEXT; v_error_detail TEXT; v_context TEXT; v_excluded_count INT; c_exclude_always TEXT = pgl_ddl_deploy.exclude_regex(); c_exception_msg TEXT = 'Deployment exception logged in pgl_ddl_deploy.exceptions'; --Configurable options in function setup c_set_config_id INT = $BUILD$||sc.id::TEXT||$BUILD$; -- Even though pglogical supports an array of sets, we only pipe DDL through one at a time -- So c_set_name is a text not text[] data type. c_set_name TEXT = '$BUILD$||set_name||$BUILD$'; c_driver pgl_ddl_deploy.driver = '$BUILD$||sc.driver||$BUILD$'; c_include_schema_regex TEXT = $BUILD$||COALESCE(''''||include_schema_regex||'''','NULL')||$BUILD$; c_lock_safe_deployment BOOLEAN = $BUILD$||lock_safe_deployment||$BUILD$; c_allow_multi_statements BOOLEAN = $BUILD$||allow_multi_statements||$BUILD$; c_include_only_repset_tables BOOLEAN = $BUILD$||include_only_repset_tables||$BUILD$; c_include_everything BOOLEAN = $BUILD$||include_everything||$BUILD$; c_queue_subscriber_failures BOOLEAN = $BUILD$||queue_subscriber_failures||$BUILD$; c_create_tags TEXT[] = '$BUILD$||create_tags::TEXT||$BUILD$'; c_blacklisted_tags TEXT[] = '$BUILD$||blacklisted_tags::TEXT||$BUILD$'; c_exclude_alter_table_subcommands TEXT[] = $BUILD$||COALESCE(quote_literal(exclude_alter_table_subcommands::TEXT),'NULL')||$BUILD$; c_signal_blocking_subscriber_sessions TEXT = $BUILD$||COALESCE(quote_literal(signal_blocking_subscriber_sessions::TEXT),'NULL')||$BUILD$; c_subscriber_lock_timeout INT = $BUILD$||COALESCE(subscriber_lock_timeout::TEXT,'NULL')||$BUILD$; --Constants based on configuration c_exec_prefix TEXT =(CASE WHEN c_lock_safe_deployment THEN 'SELECT pgl_ddl_deploy.lock_safe_executor($PGL_DDL_DEPLOY$' ELSE '' END); c_exec_suffix TEXT = (CASE WHEN c_lock_safe_deployment THEN '$PGL_DDL_DEPLOY$);' ELSE '' END); $BUILD$::TEXT AS declare_constants, $BUILD$ --If there are any matches to our replication config, get the query --This will either be sent, or logged at this point if not deployable IF (c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0 THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); v_txid = txid_current(); END IF; $BUILD$::TEXT AS shared_get_query, /**** This is the portion of the event trigger function that evaluates if SQL is appropriate to propagate, and does propagate the event. It is shared between the normal and drop event trigger functions. */ $BUILD$ /**** A multi-statement SQL command may fire this event trigger more than once This check ensures the SQL is propagated only once, if at all */ IF EXISTS (SELECT 1 FROM pgl_ddl_deploy.events WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) OR EXISTS (SELECT 1 FROM pgl_ddl_deploy.unhandled WHERE set_name = c_set_name AND txid = v_txid AND ddl_sql_raw = v_ddl_sql_raw AND pid = v_pid) THEN RETURN; END IF; /**** Get the command tags and reject blacklisted tags */ v_sql_tags:=(SELECT pgl_ddl_deploy.sql_command_tags(v_ddl_sql_raw)); IF (SELECT c_blacklisted_tags && v_sql_tags) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_command_tags', v_txid); RETURN; /**** If we are not allowing multi-statements at all, reject */ ELSEIF (SELECT ARRAY[TG_TAG]::TEXT[] <> v_sql_tags WHERE NOT c_allow_multi_statements) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'rejected_multi_statement', v_txid); RETURN; END IF; /**** If this is an ALTER TABLE statement and we are excluding any subcommand tags, process now. Note the following. Because there can be more than one subcommand, we have a limited ability to filter out subcommands until such a time as we may have a mechanism for rebuilding only the SQL we want. In other words, if we have one subcommand that we DO want (i.e. ADD COLUMN) and one we don't want (i.e. REFERENCES) in the same SQL, and we are "excluding" the latter, we can't do that exclusion safely because we WANT the ADD COLUMN statement. In such a case, we are still going to allow the DDL to go through because it's better to break replication than miss a column addition. But if the only subcommand is an excluded one, i.e. ADD CONSTRAINT, then we will indeed ignore the DDL and the function will RETURN without executing replicate_ddl_command. */ IF TG_TAG = 'ALTER TABLE' AND c_exclude_alter_table_subcommands IS NOT NULL THEN FOR v_cmd_rec IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF pgl_ddl_deploy.get_command_type(v_cmd_rec.command) = 'alter table' THEN WITH subcommands AS ( SELECT subcommand, c_exclude_alter_table_subcommands && ARRAY[subcommand] AS subcommand_is_excluded, MAX(CASE WHEN c_exclude_alter_table_subcommands && ARRAY[subcommand] THEN 0 ELSE 1 END) OVER() AS contains_any_valid_subcommand FROM unnest(pgl_ddl_deploy.get_altertable_subcmdinfo(v_cmd_rec.command)) AS subcommand ) SELECT (SELECT string_agg(subcommand,', ') FROM subcommands WHERE subcommand_is_excluded), (SELECT contains_any_valid_subcommand FROM subcommands LIMIT 1) INTO v_excluded_subcommands, v_contains_any_valid_subcommand; IF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 0 THEN RAISE LOG 'Not processing DDL due to excluded subcommand(s): %: %', v_excluded_subcommands, v_ddl_sql_raw; RETURN; ELSEIF v_excluded_subcommands IS NOT NULL AND v_contains_any_valid_subcommand = 1 THEN RAISE WARNING $INNER_BLOCK$Filtering out more than one subcommand in one ALTER TABLE is not supported. Allowing to proceed: Rejected: %, SQL: %$INNER_BLOCK$, v_excluded_subcommands, v_ddl_sql_raw; END IF; END IF; END LOOP; END IF; v_ddl_sql_sent = v_ddl_sql_raw; --If there are BEGIN/COMMIT tags, attempt to strip and reparse IF (SELECT ARRAY['BEGIN','COMMIT']::TEXT[] && v_sql_tags) THEN v_ddl_sql_sent = regexp_replace(v_ddl_sql_sent, v_ddl_strip_regex, '', 'ig'); --Sanity reparse PERFORM pgl_ddl_deploy.sql_command_tags(v_ddl_sql_sent); END IF; --Get provider name, in order only to run command on a subscriber to this provider c_provider_name:=pgl_ddl_deploy.provider_node_name(c_driver); /* Build replication DDL command which will conditionally run only on the subscriber In other words, this MUST be a no-op on the provider **Because the DDL has already run at this point (ddl_command_end)** */ v_full_ddl:=$INNER_BLOCK$ --Be sure to use provider's search_path for SQL environment consistency SET SEARCH_PATH TO $INNER_BLOCK$|| CASE WHEN COALESCE(c_search_path,'') IN('','""') THEN quote_literal('') ELSE c_search_path END||$INNER_BLOCK$; $INNER_BLOCK$||c_exec_prefix||v_ddl_sql_sent||c_exec_suffix||$INNER_BLOCK$ ; $INNER_BLOCK$; RAISE DEBUG 'v_full_ddl: %', v_full_ddl; RAISE DEBUG 'c_set_config_id: %', c_set_config_id; RAISE DEBUG 'c_set_name: %', c_set_name; RAISE DEBUG 'c_driver: %', c_driver; RAISE DEBUG 'v_ddl_sql_sent: %', v_ddl_sql_sent; v_sql:=$INNER_BLOCK$ SELECT $BUILD$||CASE WHEN sc.driver = 'native' THEN 'pgl_ddl_deploy' WHEN sc.driver = 'pglogical' THEN 'pglogical' ELSE 'ERROR-EXCEPTION' END||$BUILD$.replicate_ddl_command($REPLICATE_DDL_COMMAND$ SELECT pgl_ddl_deploy.subscriber_command ( p_provider_name := $INNER_BLOCK$||COALESCE(quote_literal(c_provider_name), 'NULL')||$INNER_BLOCK$, p_set_name := ARRAY[$INNER_BLOCK$||quote_literal(c_set_name)||$INNER_BLOCK$], p_nspname := $INNER_BLOCK$||COALESCE(quote_literal(v_nspname), 'NULL')::TEXT||$INNER_BLOCK$, p_relname := $INNER_BLOCK$||COALESCE(quote_literal(v_relname), 'NULL')::TEXT||$INNER_BLOCK$, p_ddl_sql_sent := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_ddl_sql_sent||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_full_ddl := $pgl_ddl_deploy_sql$$INNER_BLOCK$||v_full_ddl||$INNER_BLOCK$$pgl_ddl_deploy_sql$, p_pid := $INNER_BLOCK$||v_pid::TEXT||$INNER_BLOCK$, p_set_config_id := $INNER_BLOCK$||c_set_config_id::TEXT||$INNER_BLOCK$, p_queue_subscriber_failures := $INNER_BLOCK$||c_queue_subscriber_failures||$INNER_BLOCK$, p_signal_blocking_subscriber_sessions := $INNER_BLOCK$||COALESCE(quote_literal(c_signal_blocking_subscriber_sessions),'NULL')||$INNER_BLOCK$, p_lock_timeout := $INNER_BLOCK$||COALESCE(c_subscriber_lock_timeout, 3000)||$INNER_BLOCK$, p_driver := $INNER_BLOCK$||quote_literal(c_driver)||$INNER_BLOCK$ ); $REPLICATE_DDL_COMMAND$, --Pipe this DDL command through chosen replication set ARRAY['$INNER_BLOCK$||c_set_name||$INNER_BLOCK$']); $INNER_BLOCK$; RAISE DEBUG 'v_sql: %', v_sql; EXECUTE v_sql; INSERT INTO pgl_ddl_deploy.events (set_config_id, set_name, pid, executed_at, ddl_sql_raw, ddl_sql_sent, txid) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_ddl_sql_raw, v_ddl_sql_sent, v_txid); $BUILD$::TEXT AS shared_deploy_logic, $BUILD$ ELSEIF (v_match_count > 0 AND v_cmd_count <> v_match_count) THEN PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'mixed_objects', v_txid); $BUILD$::TEXT AS shared_mixed_obj_logic, $BUILD$ /** Catch any exceptions and log in a local table As a safeguard, if even the exception handler fails, exit cleanly but add a server log message **/ EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS v_context = PG_EXCEPTION_CONTEXT, v_error = MESSAGE_TEXT, v_error_detail = PG_EXCEPTION_DETAIL; BEGIN INSERT INTO pgl_ddl_deploy.exceptions (set_config_id, set_name, pid, executed_at, ddl_sql, err_msg, err_state) VALUES (c_set_config_id, c_set_name, v_pid, current_timestamp, v_sql, format('%s %s %s', v_error, v_context, v_error_detail), SQLSTATE); RAISE WARNING '%', c_exception_msg; --No matter what, don't let this function block any DDL EXCEPTION WHEN OTHERS THEN RAISE WARNING 'Unhandled exception % %', SQLERRM, SQLSTATE; END; $BUILD$::TEXT AS shared_exception_handler, $BUILD$ FROM pg_namespace n INNER JOIN pg_class c ON n.oid = c.relnamespace AND c.relpersistence = 'p' WHERE n.nspname ~* c_include_schema_regex AND n.nspname !~* c_exclude_always AND EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary) AND NOT EXISTS (SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.name = c_set_name AND rsr.relid = c.oid AND rsr.driver = c_driver) $BUILD$::TEXT AS shared_repl_set_tables, $BUILD$ SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name ~* c_include_schema_regex AND schema_name !~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_include_schema_regex AND object_identity !~* c_exclude_always) ) ) OR --include_only_repset_tables usage: ( ($BUILD$||include_only_repset_tables||$BUILD$) AND (EXISTS ( SELECT 1 FROM pgl_ddl_deploy.rep_set_table_wrapper() rsr WHERE rsr.relid = c.objid AND c.object_type in('table','table column','table constraint') AND rsr.name = '$BUILD$||sc.set_name||$BUILD$' AND rsr.driver = '$BUILD$||sc.driver||$BUILD$' ) ) ) THEN 1 ELSE 0 END) AS match_count, SUM(CASE WHEN --include_everything usage still excludes exclude_always regex: ( ($BUILD$||include_everything||$BUILD$) AND ( (schema_name ~* c_exclude_always) OR (object_type = 'schema' AND object_identity ~* c_exclude_always) ) ) THEN 1 ELSE 0 END) AS exclude_always_match_count $BUILD$::TEXT AS shared_match_count FROM pgl_ddl_deploy.rep_set_wrapper() rs INNER JOIN pgl_ddl_deploy.set_configs sc ON sc.set_name = rs.name AND sc.driver = rs.driver ) , build AS ( SELECT id, set_name, include_schema_regex, include_only_repset_tables, include_everything, signal_blocking_subscriber_sessions, subscriber_lock_timeout, auto_replication_create_function_name, auto_replication_drop_function_name, auto_replication_unsupported_function_name, auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name, CASE WHEN driver = 'pglogical' THEN '--no-op pglogical diver'::TEXT WHEN driver = 'native' THEN $BUILD$ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname = '$BUILD$||set_name||$BUILD$' AND schemaname = 'pgl_ddl_deploy' AND tablename = 'queue') THEN ALTER PUBLICATION $BUILD$||quote_ident(set_name)||$BUILD$ ADD TABLE pgl_ddl_deploy.queue; END IF; END$$; $BUILD$ END AS add_queue_table_to_replication, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$ || auto_replication_create_function_name || $BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , MAX(c.schema_name) , MAX(cl.relname) INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_nspname, v_relname FROM pg_event_trigger_ddl_commands() c LEFT JOIN LATERAL (SELECT cl.relname FROM pg_class cl WHERE cl.oid = c.objid AND c.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') -- There should only be one table modified per event trigger -- At least that's the best we will do now LIMIT 1) cl ON TRUE; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_cmd_count = v_match_count)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension FROM pg_event_trigger_ddl_commands(); /** Add table to replication set immediately, if required, and only if the set_config includes CREATE TABLE. We do not filter to tags here, because of possibility of multi-statement SQL. Optional ddl_only_replication will never auto-add tables to replication because the purpose is to only replicate keep the structure synchronized on the subscriber with no data. **/ IF c_create_tags && '{"CREATE TABLE"}' AND NOT $BUILD$||include_only_repset_tables||$BUILD$ AND NOT $BUILD$||ddl_only_replication||$BUILD$ THEN PERFORM pgl_ddl_deploy.add_table_to_replication( p_driver:=c_driver ,p_set_name:=c_set_name ,p_relation:=c.oid ,p_synchronize_data:=false ) $BUILD$||shared_repl_set_tables||$BUILD$; END IF; $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$::TEXT END AS auto_replication_function, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_drop_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ , SUM(CASE WHEN --include_schema_regex usage: ( (NOT $BUILD$||include_only_repset_tables||$BUILD$) AND ( (schema_name !~* '^(pg_catalog|pg_toast)$' AND schema_name !~* c_include_schema_regex) OR (object_type = 'schema' AND object_identity !~* '^(pg_catalog|pg_toast)$' AND object_identity !~* c_include_schema_regex) ) ) --include_only_repset_tables cannot be used with DROP because --the objects no longer exist to be checked: THEN 1 ELSE 0 END) AS excluded_count INTO v_cmd_count, v_match_count, v_exclude_always_match_count, v_excluded_count FROM pg_event_trigger_dropped_objects() c; $BUILD$||shared_get_query||$BUILD$ IF ((c_include_everything AND v_exclude_always_match_count = 0) OR (v_match_count > 0 AND v_excluded_count = 0)) THEN $BUILD$||shared_deploy_logic||$BUILD$ INSERT INTO pgl_ddl_deploy.commands (set_config_id, set_name, pid, txid, classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension) SELECT c_set_config_id, c_set_name, v_pid, v_txid, classid, objid, objsubid, TG_TAG, object_type, schema_name, object_identity, NULL FROM pg_event_trigger_dropped_objects(); $BUILD$||shared_mixed_obj_logic||$BUILD$ END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_drop_function, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE OR REPLACE FUNCTION $BUILD$||auto_replication_unsupported_function_name||$BUILD$() RETURNS EVENT_TRIGGER AS $BODY$ DECLARE $BUILD$||declare_constants||$BUILD$ BEGIN /***** Only enter execution body if object being altered is relevant */ SELECT COUNT(1) , $BUILD$||shared_match_count||$BUILD$ INTO v_cmd_count, v_match_count, v_exclude_always_match_count FROM pg_event_trigger_ddl_commands() c; IF ((c_include_everything AND v_exclude_always_match_count = 0) OR v_match_count > 0) THEN v_ddl_sql_raw = pgl_ddl_deploy.current_query(); PERFORM pgl_ddl_deploy.log_unhandled( c_set_config_id, c_set_name, v_pid, v_ddl_sql_raw, TG_TAG, 'unsupported_command', v_txid); END IF; $BUILD$||shared_exception_handler||$BUILD$ END; $BODY$ LANGUAGE plpgsql; $BUILD$ END AS auto_replication_unsupported_function, CASE WHEN create_tags IS NULL THEN '--no-op-null-create-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(create_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$ || auto_replication_create_function_name || $BUILD$(); $BUILD$::TEXT END AS auto_replication_trigger, CASE WHEN drop_tags IS NULL THEN '--no-op-null-drop-tags'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ON sql_drop WHEN TAG IN('$BUILD$||array_to_string(drop_tags,$$','$$)||$BUILD$') --TODO - CREATE INDEX HANDLING EXECUTE PROCEDURE $BUILD$||auto_replication_drop_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_drop_trigger, CASE WHEN include_only_repset_tables THEN '--no-op-only-repset-tables'::TEXT ELSE $BUILD$ CREATE EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ON ddl_command_end WHEN TAG IN('$BUILD$||array_to_string(pgl_ddl_deploy.unsupported_tags(),$$','$$)||$BUILD$') EXECUTE PROCEDURE $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$::TEXT END AS auto_replication_unsupported_trigger, $BUILD$ DROP TABLE IF EXISTS tmp_objs; CREATE TEMP TABLE tmp_objs (obj_type, obj_name) AS ( VALUES ('EVENT TRIGGER','$BUILD$||auto_replication_create_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_drop_trigger_name||$BUILD$'), ('EVENT TRIGGER','$BUILD$||auto_replication_unsupported_trigger_name||$BUILD$'), ('FUNCTION','$BUILD$||auto_replication_create_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_drop_function_name||$BUILD$()'), ('FUNCTION','$BUILD$||auto_replication_unsupported_function_name||$BUILD$()') ); SELECT pgl_ddl_deploy.drop_ext_object(obj_type, obj_name) FROM tmp_objs; DROP EVENT TRIGGER IF EXISTS $BUILD$||auto_replication_create_trigger_name||', '||auto_replication_drop_trigger_name||', '||auto_replication_unsupported_trigger_name||$BUILD$; DROP FUNCTION IF EXISTS $BUILD$||auto_replication_create_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_drop_function_name||$BUILD$(); DROP FUNCTION IF EXISTS $BUILD$||auto_replication_unsupported_function_name||$BUILD$(); $BUILD$ AS undeploy_sql FROM vars) SELECT b.id, b.set_name, b.include_schema_regex, b.include_only_repset_tables, b.include_everything, b.signal_blocking_subscriber_sessions, b.subscriber_lock_timeout, b.auto_replication_create_function_name, b.auto_replication_drop_function_name, b.auto_replication_unsupported_function_name, b.auto_replication_create_trigger_name, b.auto_replication_drop_trigger_name, b.auto_replication_unsupported_trigger_name, b.auto_replication_function, b.auto_replication_drop_function, b.auto_replication_unsupported_function, b.auto_replication_trigger, b.auto_replication_drop_trigger, b.auto_replication_unsupported_trigger, b.undeploy_sql, b.undeploy_sql|| b.add_queue_table_to_replication||$BUILD$ $BUILD$||auto_replication_function||$BUILD$ $BUILD$||auto_replication_drop_function||$BUILD$ $BUILD$||auto_replication_unsupported_function||$BUILD$ $BUILD$||auto_replication_trigger||$BUILD$ $BUILD$||auto_replication_drop_trigger||$BUILD$ $BUILD$||auto_replication_unsupported_trigger||$BUILD$ SELECT pgl_ddl_deploy.add_ext_object(obj_type, obj_name) FROM tmp_objs; $BUILD$ AS deploy_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ DISABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ DISABLE; $BUILD$ AS disable_sql, $BUILD$ ALTER EVENT TRIGGER $BUILD$||auto_replication_create_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_drop_trigger_name||$BUILD$ ENABLE; ALTER EVENT TRIGGER $BUILD$||auto_replication_unsupported_trigger_name||$BUILD$ ENABLE; $BUILD$ AS enable_sql, EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname IN( auto_replication_create_trigger_name, auto_replication_drop_trigger_name, auto_replication_unsupported_trigger_name ) AND evtenabled IN('O','R','A') ) AS is_deployed FROM build b;