From 238b0717b0f4371c3e1d84fc0d0857004baec38b Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Mon, 6 Mar 2023 13:15:58 +0100 Subject: [PATCH v4] Add GUC for temporarily disabling event triggers In order to troubleshoot misbehaving or buggy event triggers, the documented advice is to enter single-user mode. In an attempt to reduce the number of situations where single-user mode is required for non-extraordinary maintenance, this GUC allows to temporarily suspend event triggers. This was originally extracted from a larger patchset which aimed at supporting event triggers on login events. Reviewed-by: Ted Yu Reviewed-by: Mikhail Gribkov Discussion: https://postgr.es/m/9140106E-F9BF-4D85-8FC8-F2D3C094A6D9@yesql.se --- doc/src/sgml/config.sgml | 23 ++++++++ doc/src/sgml/ref/create_event_trigger.sgml | 4 +- src/backend/commands/event_trigger.c | 53 ++++++++++++++++--- src/backend/utils/misc/guc_tables.c | 21 ++++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/commands/event_trigger.h | 12 +++++ src/test/regress/expected/event_trigger.out | 29 ++++++++++ src/test/regress/sql/event_trigger.sql | 32 +++++++++++ 8 files changed, 167 insertions(+), 8 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index e5c41cc6c6..27c63a8afd 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -9519,6 +9519,29 @@ SET XML OPTION { DOCUMENT | CONTENT }; + + ignore_event_trigger (enum) + + ignore_event_trigger + configuration parameter + + + + + Allows to temporarily disable event triggers from executing in order + to troubleshoot and repair faulty event triggers. The value matches + the type of event trigger to be ignored: + ddl_command_start, ddl_command_end, + table_rewrite and sql_drop. + Additionally, all event triggers can be disabled by setting it to + all. Setting the value to none + will ensure that all event triggers are enabled, this is the default + value. Only superusers and users with the appropriate + SET privilege can change this setting. + + + + diff --git a/doc/src/sgml/ref/create_event_trigger.sgml b/doc/src/sgml/ref/create_event_trigger.sgml index 22c8119198..f6922c3de3 100644 --- a/doc/src/sgml/ref/create_event_trigger.sgml +++ b/doc/src/sgml/ref/create_event_trigger.sgml @@ -123,7 +123,9 @@ CREATE EVENT TRIGGER name Event triggers are disabled in single-user mode (see ). If an erroneous event trigger disables the database so much that you can't even drop the trigger, restart in - single-user mode and you'll be able to do that. + single-user mode and you'll be able to do that. Event triggers can also be + temporarily disabled for such troubleshooting, see + . diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index d4b00d1a82..e37fffd21b 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -72,6 +72,9 @@ typedef struct EventTriggerQueryState static EventTriggerQueryState *currentEventTriggerState = NULL; +/* GUC parameter */ +int ignore_event_trigger = IGNORE_EVENT_TRIGGER_NONE; + /* Support for dropped objects */ typedef struct SQLDropObject { @@ -100,6 +103,7 @@ static void validate_table_rewrite_tags(const char *filtervar, List *taglist); static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata); static const char *stringify_grant_objtype(ObjectType objtype); static const char *stringify_adefprivs_objtype(ObjectType objtype); +static bool ignore_event_trigger_check(EventTriggerEvent event); /* * Create an event trigger. @@ -657,8 +661,11 @@ EventTriggerDDLCommandStart(Node *parsetree) * wherein event triggers are disabled. (Or we could implement * heapscan-and-sort logic for that case, but having disaster recovery * scenarios depend on code that's otherwise untested isn't appetizing.) + * + * Additionally, event triggers can be disabled with a superuser-only GUC + * to make fixing database easier as per 1 above. */ - if (!IsUnderPostmaster) + if (!IsUnderPostmaster || ignore_event_trigger_check(EVT_DDLCommandStart)) return; runlist = EventTriggerCommonSetup(parsetree, @@ -692,9 +699,9 @@ EventTriggerDDLCommandEnd(Node *parsetree) /* * See EventTriggerDDLCommandStart for a discussion about why event - * triggers are disabled in single user mode. + * triggers are disabled in single user mode or via GUC. */ - if (!IsUnderPostmaster) + if (!IsUnderPostmaster || ignore_event_trigger_check(EVT_DDLCommandEnd)) return; /* @@ -740,9 +747,9 @@ EventTriggerSQLDrop(Node *parsetree) /* * See EventTriggerDDLCommandStart for a discussion about why event - * triggers are disabled in single user mode. + * triggers are disabled in single user mode or via a GUC. */ - if (!IsUnderPostmaster) + if (!IsUnderPostmaster || ignore_event_trigger_check(EVT_SQLDrop)) return; /* @@ -811,9 +818,9 @@ EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason) /* * See EventTriggerDDLCommandStart for a discussion about why event - * triggers are disabled in single user mode. + * triggers are disabled in single user mode or via a GUC. */ - if (!IsUnderPostmaster) + if (!IsUnderPostmaster || ignore_event_trigger_check(EVT_TableRewrite)) return; /* @@ -2175,3 +2182,35 @@ stringify_adefprivs_objtype(ObjectType objtype) return "???"; /* keep compiler quiet */ } + + +/* + * Checks whether the specified event is ignored by the ignore_event_trigger + * GUC or not. + */ +static bool +ignore_event_trigger_check(EventTriggerEvent event) +{ + if (ignore_event_trigger == IGNORE_EVENT_TRIGGER_NONE) + return false; + + if (ignore_event_trigger == IGNORE_EVENT_TRIGGER_ALL) + return true; + + switch (event) + { + case EVT_DDLCommandStart: + return ignore_event_trigger == IGNORE_EVENT_TRIGGER_DDL_START; + case EVT_DDLCommandEnd: + return ignore_event_trigger == IGNORE_EVENT_TRIGGER_DDL_END; + case EVT_SQLDrop: + return ignore_event_trigger == IGNORE_EVENT_TRIGGER_SQL_DROP; + case EVT_TableRewrite: + return ignore_event_trigger == IGNORE_EVENT_TRIGGER_TABLE_REWRITE; + + default: + elog(ERROR, "unsupport event trigger: %d", event); + } + + return false; /* unreachable */ +} diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 1c0583fe26..970fab3e2e 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -37,6 +37,7 @@ #include "catalog/namespace.h" #include "catalog/storage.h" #include "commands/async.h" +#include "commands/event_trigger.h" #include "commands/tablespace.h" #include "commands/trigger.h" #include "commands/user.h" @@ -453,6 +454,16 @@ static const struct config_enum_entry wal_compression_options[] = { {NULL, 0, false} }; +static const struct config_enum_entry ignore_event_trigger_options[] = { + {"none", IGNORE_EVENT_TRIGGER_NONE, false}, + {"all", IGNORE_EVENT_TRIGGER_ALL, false}, + {"ddl_command_start", IGNORE_EVENT_TRIGGER_DDL_START, false}, + {"ddl_command_end", IGNORE_EVENT_TRIGGER_DDL_END, false}, + {"table_rewrite", IGNORE_EVENT_TRIGGER_TABLE_REWRITE, false}, + {"sql_drop", IGNORE_EVENT_TRIGGER_SQL_DROP, false}, + {NULL, 0, false} +}; + /* * Options for enum values stored in other modules */ @@ -4771,6 +4782,16 @@ struct config_enum ConfigureNamesEnum[] = NULL, NULL, NULL }, + { + {"ignore_event_trigger", PGC_SUSET, CLIENT_CONN_STATEMENT, + gettext_noop("Disable event triggers for the duration of the session."), + NULL + }, + &ignore_event_trigger, + IGNORE_EVENT_TRIGGER_NONE, ignore_event_trigger_options, + NULL, NULL, NULL + }, + { {"wal_level", PGC_POSTMASTER, WAL_SETTINGS, gettext_noop("Sets the level of information written to the WAL."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index d06074b86f..12be5f0acd 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -705,6 +705,7 @@ #xmloption = 'content' #gin_pending_list_limit = 4MB #createrole_self_grant = '' # set and/or inherit +#ignore_event_trigger = none # - Locale and Formatting - diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index 5ed6ece555..85b93a8320 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -29,6 +29,18 @@ typedef struct EventTriggerData CommandTag tag; } EventTriggerData; +typedef enum ignore_event_trigger_events +{ + IGNORE_EVENT_TRIGGER_NONE, + IGNORE_EVENT_TRIGGER_DDL_START, + IGNORE_EVENT_TRIGGER_DDL_END, + IGNORE_EVENT_TRIGGER_TABLE_REWRITE, + IGNORE_EVENT_TRIGGER_SQL_DROP, + IGNORE_EVENT_TRIGGER_ALL +} IgnoreEventTriggersEvents; + +extern PGDLLIMPORT int ignore_event_trigger; + #define AT_REWRITE_ALTER_PERSISTENCE 0x01 #define AT_REWRITE_DEFAULT_VAL 0x02 #define AT_REWRITE_COLUMN_REWRITE 0x04 diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index 5a10958df5..34c3b80e4f 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -614,3 +614,32 @@ SELECT DROP EVENT TRIGGER start_rls_command; DROP EVENT TRIGGER end_rls_command; DROP EVENT TRIGGER sql_drop_command; +-- Check the GUC for disabling event triggers +CREATE FUNCTION test_event_trigger_guc() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +DECLARE + obj record; +BEGIN + FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() + LOOP + RAISE NOTICE '% dropped %', tg_tag, obj.object_type; + END LOOP; +END; +$$; +CREATE EVENT TRIGGER test_event_trigger_guc + ON sql_drop + WHEN TAG IN ('DROP POLICY') EXECUTE FUNCTION test_event_trigger_guc(); +SET ignore_event_trigger = 'none'; +CREATE POLICY pguc ON event_trigger_test USING (FALSE); +DROP POLICY pguc ON event_trigger_test; +NOTICE: DROP POLICY dropped policy +CREATE POLICY pguc ON event_trigger_test USING (FALSE); +SET ignore_event_trigger = 'sql_drop'; +DROP POLICY pguc ON event_trigger_test; +CREATE POLICY pguc ON event_trigger_test USING (FALSE); +SET ignore_event_trigger = 'all'; +DROP POLICY pguc ON event_trigger_test; +CREATE POLICY pguc ON event_trigger_test USING (FALSE); +SET ignore_event_trigger = 'ddl_command_start'; +DROP POLICY pguc ON event_trigger_test; +NOTICE: DROP POLICY dropped policy diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql index 1aeaddbe71..eb18d23fd3 100644 --- a/src/test/regress/sql/event_trigger.sql +++ b/src/test/regress/sql/event_trigger.sql @@ -471,3 +471,35 @@ SELECT DROP EVENT TRIGGER start_rls_command; DROP EVENT TRIGGER end_rls_command; DROP EVENT TRIGGER sql_drop_command; + +-- Check the GUC for disabling event triggers +CREATE FUNCTION test_event_trigger_guc() RETURNS event_trigger +LANGUAGE plpgsql AS $$ +DECLARE + obj record; +BEGIN + FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() + LOOP + RAISE NOTICE '% dropped %', tg_tag, obj.object_type; + END LOOP; +END; +$$; +CREATE EVENT TRIGGER test_event_trigger_guc + ON sql_drop + WHEN TAG IN ('DROP POLICY') EXECUTE FUNCTION test_event_trigger_guc(); + +SET ignore_event_trigger = 'none'; +CREATE POLICY pguc ON event_trigger_test USING (FALSE); +DROP POLICY pguc ON event_trigger_test; + +CREATE POLICY pguc ON event_trigger_test USING (FALSE); +SET ignore_event_trigger = 'sql_drop'; +DROP POLICY pguc ON event_trigger_test; + +CREATE POLICY pguc ON event_trigger_test USING (FALSE); +SET ignore_event_trigger = 'all'; +DROP POLICY pguc ON event_trigger_test; + +CREATE POLICY pguc ON event_trigger_test USING (FALSE); +SET ignore_event_trigger = 'ddl_command_start'; +DROP POLICY pguc ON event_trigger_test; -- 2.32.1 (Apple Git-133)