pax_global_header 0000666 0000000 0000000 00000000064 14531715453 0014522 g ustar 00root root 0000000 0000000 52 comment=f6a35350d495f8fcc2bf45d7cee2e45392097314
pgl_ddl_deploy-2.2.1/ 0000775 0000000 0000000 00000000000 14531715453 0014505 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/LICENSE 0000664 0000000 0000000 00000002051 14531715453 0015510 0 ustar 00root root 0000000 0000000 Copyright 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/Makefile 0000664 0000000 0000000 00000004772 14531715453 0016157 0 ustar 00root root 0000000 0000000 EXTENSION = 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.md 0000664 0000000 0000000 00000112450 14531715453 0015767 0 ustar 00root root 0000000 0000000 # 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.py 0000775 0000000 0000000 00000001220 14531715453 0022460 0 ustar 00root root 0000000 0000000 #!/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.c 0000664 0000000 0000000 00000017763 14531715453 0017135 0 ustar 00root root 0000000 0000000 /*----------------------------------------------------------------------
* 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/ 0000775 0000000 0000000 00000000000 14531715453 0015727 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/debian/README.md 0000664 0000000 0000000 00000001003 14531715453 0017200 0 ustar 00root root 0000000 0000000 # 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/changelog 0000664 0000000 0000000 00000007226 14531715453 0017610 0 ustar 00root root 0000000 0000000 pgl-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/control 0000664 0000000 0000000 00000001200 14531715453 0017323 0 ustar 00root root 0000000 0000000 Source: 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.in 0000664 0000000 0000000 00000001225 14531715453 0017737 0 ustar 00root root 0000000 0000000 Source: 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/copyright 0000664 0000000 0000000 00000004214 14531715453 0017663 0 ustar 00root root 0000000 0000000 Format: 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/docs 0000664 0000000 0000000 00000000012 14531715453 0016573 0 ustar 00root root 0000000 0000000 README.md
pgl_ddl_deploy-2.2.1/debian/pgversions 0000664 0000000 0000000 00000000004 14531715453 0020043 0 ustar 00root root 0000000 0000000 11+
pgl_ddl_deploy-2.2.1/debian/rules 0000775 0000000 0000000 00000000214 14531715453 0017004 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 14531715453 0017227 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/debian/source/format 0000664 0000000 0000000 00000000014 14531715453 0020435 0 ustar 00root root 0000000 0000000 3.0 (quilt)
pgl_ddl_deploy-2.2.1/debian/tests/ 0000775 0000000 0000000 00000000000 14531715453 0017071 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/debian/tests/control 0000664 0000000 0000000 00000000247 14531715453 0020477 0 ustar 00root root 0000000 0000000 # 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.in 0000664 0000000 0000000 00000000265 14531715453 0021104 0 ustar 00root root 0000000 0000000 # 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/installcheck 0000775 0000000 0000000 00000000136 14531715453 0021463 0 ustar 00root root 0000000 0000000 #!/bin/sh
pg_buildext -o shared_preload_libraries=pglogical -o wal_level=logical installcheck
pgl_ddl_deploy-2.2.1/debian/upstream/ 0000775 0000000 0000000 00000000000 14531715453 0017567 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/debian/upstream/metadata 0000664 0000000 0000000 00000000363 14531715453 0021274 0 ustar 00root root 0000000 0000000 ---
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/watch 0000664 0000000 0000000 00000000113 14531715453 0016753 0 ustar 00root root 0000000 0000000 version=4
https://github.com/enova/pgl_ddl_deploy/releases .*/v(.*).tar.gz
pgl_ddl_deploy-2.2.1/duplicate_tests_for_native.py 0000775 0000000 0000000 00000015336 14531715453 0022502 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 14531715453 0016306 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/expected/01_create_ext.out 0000664 0000000 0000000 00000000304 14531715453 0021457 0 ustar 00root root 0000000 0000000 -- 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.out 0000664 0000000 0000000 00000005104 14531715453 0020500 0 ustar 00root root 0000000 0000000 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;
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.out 0000664 0000000 0000000 00000002725 14531715453 0021607 0 ustar 00root root 0000000 0000000 INSERT 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.out 0000664 0000000 0000000 00000002273 14531715453 0020642 0 ustar 00root root 0000000 0000000 --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.out 0000664 0000000 0000000 00000004033 14531715453 0022200 0 ustar 00root root 0000000 0000000 --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.out 0000664 0000000 0000000 00000125357 14531715453 0021007 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000047671 14531715453 0020515 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000050051 14531715453 0020717 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000004157 14531715453 0020443 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000002324 14531715453 0020776 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000015023 14531715453 0021740 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000000321 14531715453 0022330 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000002020 14531715453 0021151 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000001161 14531715453 0022653 0 ustar 00root root 0000000 0000000 SELECT 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.out 0000664 0000000 0000000 00000026511 14531715453 0021674 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000012065 14531715453 0022673 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000006560 14531715453 0022377 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000000605 14531715453 0025167 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000002615 14531715453 0025174 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000006062 14531715453 0025176 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000013145 14531715453 0025167 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000013201 14531715453 0025400 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000000141 14531715453 0023253 0 ustar 00root root 0000000 0000000 CREATE 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.out 0000664 0000000 0000000 00000010066 14531715453 0021645 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000006454 14531715453 0021635 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00001471067 14531715453 0021712 0 ustar 00root root 0000000 0000000 --****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.out 0000664 0000000 0000000 00000041343 14531715453 0021634 0 ustar 00root root 0000000 0000000 -- 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.out 0000664 0000000 0000000 00000005517 14531715453 0021367 0 ustar 00root root 0000000 0000000 --****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.out 0000664 0000000 0000000 00000002077 14531715453 0022164 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000004067 14531715453 0021642 0 ustar 00root root 0000000 0000000 -- 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.out 0000664 0000000 0000000 00000001702 14531715453 0021474 0 ustar 00root root 0000000 0000000 -- 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.out 0000664 0000000 0000000 00000005526 14531715453 0020511 0 ustar 00root root 0000000 0000000 DO $$
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.out 0000664 0000000 0000000 00000002725 14531715453 0021610 0 ustar 00root root 0000000 0000000 INSERT 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.out 0000664 0000000 0000000 00000004346 14531715453 0022210 0 ustar 00root root 0000000 0000000 --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.out 0000664 0000000 0000000 00000125357 14531715453 0021010 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000047671 14531715453 0020516 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000050051 14531715453 0020720 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000004157 14531715453 0020444 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000002324 14531715453 0020777 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000015023 14531715453 0021741 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000000321 14531715453 0022342 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000002020 14531715453 0021163 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000001161 14531715453 0022654 0 ustar 00root root 0000000 0000000 SELECT 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.out 0000664 0000000 0000000 00000026511 14531715453 0021675 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000012372 14531715453 0022675 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000007224 14531715453 0022376 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000000605 14531715453 0025170 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000002615 14531715453 0025175 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000006062 14531715453 0025177 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000013145 14531715453 0025201 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000013201 14531715453 0025412 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000000141 14531715453 0023265 0 ustar 00root root 0000000 0000000 CREATE 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.out 0000664 0000000 0000000 00000010066 14531715453 0021646 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000006703 14531715453 0021633 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00001471161 14531715453 0021706 0 ustar 00root root 0000000 0000000 --****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.out 0000664 0000000 0000000 00000041343 14531715453 0021635 0 ustar 00root root 0000000 0000000 -- 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.out 0000664 0000000 0000000 00000006037 14531715453 0021366 0 ustar 00root root 0000000 0000000 --****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.out 0000664 0000000 0000000 00000002077 14531715453 0022165 0 ustar 00root root 0000000 0000000 SET 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.out 0000664 0000000 0000000 00000004067 14531715453 0021643 0 ustar 00root root 0000000 0000000 -- 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.out 0000664 0000000 0000000 00000004611 14531715453 0022540 0 ustar 00root root 0000000 0000000 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/functions/ 0000775 0000000 0000000 00000000000 14531715453 0016515 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/functions/add_ext_object.sql 0000664 0000000 0000000 00000000353 14531715453 0022175 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000003776 14531715453 0021024 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/add_table_to_replication.sql 0000664 0000000 0000000 00000003140 14531715453 0024226 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/blacklisted_tags.sql 0000664 0000000 0000000 00000000472 14531715453 0022540 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/common_exclude_alter_table_subcommands.sql 0000664 0000000 0000000 00000001561 14531715453 0027173 0 ustar 00root root 0000000 0000000 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; pgl_ddl_deploy-2.2.1/functions/deploy.sql 0000664 0000000 0000000 00000001542 14531715453 0020534 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000700 14531715453 0022550 0 ustar 00root root 0000000 0000000 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$;
pgl_ddl_deploy-2.2.1/functions/deployment_check_count.sql 0000664 0000000 0000000 00000003767 14531715453 0024000 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/deployment_check_wrapper.sql 0000664 0000000 0000000 00000003353 14531715453 0024317 0 ustar 00root root 0000000 0000000 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$;
pgl_ddl_deploy-2.2.1/functions/disable.sql 0000664 0000000 0000000 00000000413 14531715453 0020637 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000355 14531715453 0022413 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000656 14531715453 0020473 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000363 14531715453 0022063 0 ustar 00root root 0000000 0000000 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$
; pgl_ddl_deploy-2.2.1/functions/execute_queued_ddl.sql 0000664 0000000 0000000 00000003644 14531715453 0023102 0 ustar 00root root 0000000 0000000 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/functions/fail_queued_attempt.sql 0000664 0000000 0000000 00000001665 14531715453 0023267 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000272 14531715453 0024426 0 ustar 00root root 0000000 0000000 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;
pgl_ddl_deploy-2.2.1/functions/get_command_tag.sql 0000664 0000000 0000000 00000000230 14531715453 0022341 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000232 14531715453 0022551 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000001343 14531715453 0022075 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/kill_blockers.sql 0000664 0000000 0000000 00000006227 14531715453 0022064 0 ustar 00root root 0000000 0000000 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;
pgl_ddl_deploy-2.2.1/functions/lock_safe_executor.sql 0000664 0000000 0000000 00000000641 14531715453 0023103 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000001225 14531715453 0022041 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000001534 14531715453 0025073 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/override.sql 0000664 0000000 0000000 00000000213 14531715453 0021051 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000760 14531715453 0023100 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/queue_ddl_message_type.sql 0000664 0000000 0000000 00000000234 14531715453 0023751 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000505 14531715453 0022045 0 ustar 00root root 0000000 0000000 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;
pgl_ddl_deploy-2.2.1/functions/rep_set_table_wrapper.sql 0000664 0000000 0000000 00000005356 14531715453 0023617 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/rep_set_wrapper.sql 0000664 0000000 0000000 00000002567 14531715453 0022451 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/replicate_ddl_command.sql 0000664 0000000 0000000 00000001055 14531715453 0023530 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/resolve_exception.sql 0000664 0000000 0000000 00000000624 14531715453 0022775 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000623 14531715453 0022740 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000001342 14531715453 0024502 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000004174 14531715453 0023475 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000001243 14531715453 0022220 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000304 14531715453 0025135 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000763 14531715453 0022561 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000256 14531715453 0022554 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000646 14531715453 0023405 0 ustar 00root root 0000000 0000000 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-2.2.1/functions/standard_drop_tags.sql 0000664 0000000 0000000 00000000400 14531715453 0023072 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000271 14531715453 0024477 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000012353 14531715453 0023103 0 ustar 00root root 0000000 0000000 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;
pgl_ddl_deploy-2.2.1/functions/toggle_ext_object.sql 0000664 0000000 0000000 00000001507 14531715453 0022730 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000000656 14531715453 0021104 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000002616 14531715453 0021567 0 ustar 00root root 0000000 0000000 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$
;
pgl_ddl_deploy-2.2.1/functions/unsupported_tags.sql 0000664 0000000 0000000 00000000275 14531715453 0022650 0 ustar 00root root 0000000 0000000 CREATE 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.py 0000775 0000000 0000000 00000006074 14531715453 0022324 0 ustar 00root root 0000000 0000000 #!/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.sql 0000664 0000000 0000000 00000126224 14531715453 0021141 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000074654 14531715453 0020600 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000054311 14531715453 0021140 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000223100 14531715453 0020557 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000054274 14531715453 0021152 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000277411 14531715453 0020576 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000102555 14531715453 0021150 0 ustar 00root root 0000000 0000000 /* 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.sql 0000664 0000000 0000000 00000353705 14531715453 0020600 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000117477 14531715453 0021163 0 ustar 00root root 0000000 0000000 /* 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.sql 0000664 0000000 0000000 00000456462 14531715453 0020605 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000075013 14531715453 0021152 0 ustar 00root root 0000000 0000000 /* 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.sql 0000664 0000000 0000000 00000576161 14531715453 0020605 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000003231 14531715453 0021145 0 ustar 00root root 0000000 0000000 /* 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.sql 0000664 0000000 0000000 00000673174 14531715453 0020610 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000146562 14531715453 0021157 0 ustar 00root root 0000000 0000000 /* 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.sql 0000664 0000000 0000000 00000673603 14531715453 0020606 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000004133 14531715453 0021135 0 ustar 00root root 0000000 0000000 /* 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.sql 0000664 0000000 0000000 00000320164 14531715453 0020567 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000110037 14531715453 0021140 0 ustar 00root root 0000000 0000000 /* 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.sql 0000664 0000000 0000000 00000324317 14531715453 0020574 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000434356 14531715453 0020602 0 ustar 00root root 0000000 0000000 -- 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.sh 0000775 0000000 0000000 00000002201 14531715453 0021712 0 ustar 00root root 0000000 0000000 #!/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.c 0000664 0000000 0000000 00000005332 14531715453 0017635 0 ustar 00root root 0000000 0000000 #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.control 0000664 0000000 0000000 00000000255 14531715453 0021072 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14531715453 0015745 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/schema/1.4.sql 0000664 0000000 0000000 00000001525 14531715453 0016773 0 ustar 00root root 0000000 0000000 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;
pgl_ddl_deploy-2.2.1/schema/1.4_post.sql 0000664 0000000 0000000 00000000543 14531715453 0020037 0 ustar 00root root 0000000 0000000 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/schema/1.5.sql 0000664 0000000 0000000 00000004070 14531715453 0016772 0 ustar 00root root 0000000 0000000 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
);
pgl_ddl_deploy-2.2.1/schema/1.5_post.sql 0000664 0000000 0000000 00000001033 14531715453 0020033 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000001462 14531715453 0016775 0 ustar 00root root 0000000 0000000 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;
pgl_ddl_deploy-2.2.1/schema/1.6_post.sql 0000664 0000000 0000000 00000000275 14531715453 0020043 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000010012 14531715453 0016757 0 ustar 00root root 0000000 0000000 /*
* 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.sql 0000664 0000000 0000000 00000002057 14531715453 0020036 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000001651 14531715453 0016772 0 ustar 00root root 0000000 0000000 /*
* 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.sql 0000664 0000000 0000000 00000002057 14531715453 0020040 0 ustar 00root root 0000000 0000000 -- 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/ 0000775 0000000 0000000 00000000000 14531715453 0015304 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/sql/01_create_ext.sql 0000664 0000000 0000000 00000000304 14531715453 0020445 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000005053 14531715453 0017471 0 ustar 00root root 0000000 0000000 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;
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.sql 0000664 0000000 0000000 00000002626 14531715453 0020575 0 ustar 00root root 0000000 0000000 INSERT 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.sql 0000664 0000000 0000000 00000002047 14531715453 0017627 0 ustar 00root root 0000000 0000000 --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.sql 0000664 0000000 0000000 00000002001 14531715453 0021157 0 ustar 00root root 0000000 0000000 --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.sql 0000664 0000000 0000000 00000007460 14531715453 0017767 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000006346 14531715453 0017475 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000723 14531715453 0017424 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000527 14531715453 0017767 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000001221 14531715453 0020721 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000325 14531715453 0021322 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000505 14531715453 0020145 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000421 14531715453 0021637 0 ustar 00root root 0000000 0000000 SELECT 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.sql 0000664 0000000 0000000 00000002146 14531715453 0020660 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000007140 14531715453 0021657 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000001460 14531715453 0021357 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000606 14531715453 0024156 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000002167 14531715453 0024164 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000002034 14531715453 0024157 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000003107 14531715453 0024152 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000141 14531715453 0022241 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000001570 14531715453 0020633 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000003452 14531715453 0020616 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000015061 14531715453 0020663 0 ustar 00root root 0000000 0000000 --****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.sql 0000664 0000000 0000000 00000021407 14531715453 0020621 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000005177 14531715453 0020357 0 ustar 00root root 0000000 0000000 --****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.sql 0000664 0000000 0000000 00000001275 14531715453 0021151 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000003554 14531715453 0020630 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000001635 14531715453 0020467 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000005472 14531715453 0017477 0 ustar 00root root 0000000 0000000 DO $$
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.sql 0000664 0000000 0000000 00000002626 14531715453 0020576 0 ustar 00root root 0000000 0000000 INSERT 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.sql 0000664 0000000 0000000 00000002246 14531715453 0021173 0 ustar 00root root 0000000 0000000 --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.sql 0000664 0000000 0000000 00000007460 14531715453 0017770 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000006346 14531715453 0017476 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000723 14531715453 0017425 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000527 14531715453 0017770 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000001221 14531715453 0020722 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000325 14531715453 0021334 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000505 14531715453 0020157 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000421 14531715453 0021640 0 ustar 00root root 0000000 0000000 SELECT 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.sql 0000664 0000000 0000000 00000002146 14531715453 0020661 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000007443 14531715453 0021666 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000002125 14531715453 0021357 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000606 14531715453 0024157 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000002167 14531715453 0024165 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000002034 14531715453 0024160 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000003107 14531715453 0024164 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000000141 14531715453 0022253 0 ustar 00root root 0000000 0000000 CREATE 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.sql 0000664 0000000 0000000 00000001570 14531715453 0020634 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000003677 14531715453 0020630 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000015106 14531715453 0020664 0 ustar 00root root 0000000 0000000 --****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.sql 0000664 0000000 0000000 00000021407 14531715453 0020622 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000005450 14531715453 0020352 0 ustar 00root root 0000000 0000000 --****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.sql 0000664 0000000 0000000 00000001275 14531715453 0021152 0 ustar 00root root 0000000 0000000 SET 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.sql 0000664 0000000 0000000 00000003554 14531715453 0020631 0 ustar 00root root 0000000 0000000 -- 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.sql 0000664 0000000 0000000 00000004621 14531715453 0021527 0 ustar 00root root 0000000 0000000
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.sh 0000775 0000000 0000000 00000001724 14531715453 0020607 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 14531715453 0016333 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/triggers/unique_tags.sql 0000664 0000000 0000000 00000000215 14531715453 0021376 0 ustar 00root root 0000000 0000000 CREATE 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/ 0000775 0000000 0000000 00000000000 14531715453 0015642 5 ustar 00root root 0000000 0000000 pgl_ddl_deploy-2.2.1/views/event_trigger_schema.sql 0000664 0000000 0000000 00000065075 14531715453 0022564 0 ustar 00root root 0000000 0000000 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;