From d0f8470100d1f7cc9041d53394c227ce28cb0680 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Thu, 3 Nov 2022 16:24:06 +0100
Subject: [PATCH v2] 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
suspending event triggers.

This was extracted from a larger patchset which aimed at supporting
event triggers on login events.
---
 doc/src/sgml/config.sgml                      | 19 +++++++++
 doc/src/sgml/ref/create_event_trigger.sgml    |  4 +-
 src/backend/commands/event_trigger.c          | 40 +++++++++++++++----
 src/backend/utils/misc/guc_tables.c           | 17 ++++++++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 src/include/commands/event_trigger.h          |  8 ++++
 src/test/regress/expected/event_trigger.out   | 21 ++++++++++
 src/test/regress/sql/event_trigger.sql        | 22 ++++++++++
 8 files changed, 124 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 82df89b1a9..0c9b488211 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9363,6 +9363,25 @@ SET XML OPTION { DOCUMENT | CONTENT };
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-ignore-event-trigger" xreflabel="ignore_event_trigger">
+      <term><varname>ignore_event_trigger</varname> (<type>enum</type>)
+      <indexterm>
+       <primary><varname>ignore_event_trigger</varname></primary>
+       <secondary>configuration parameter</secondary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Allows to temporarily disable event triggers from executing in order
+        to troubleshoot and repair faulty event triggers.  The default value
+        is <literal>none</literal>, with which all event triggers are enabled.
+        When set to <literal>all</literal> then all event triggers will be
+        disabled.  Only superusers and users with the appropriate
+        <literal>SET</literal> privilege can change this setting.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
      <sect2 id="runtime-config-client-format">
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 <replaceable class="parameter">name</replaceable>
    Event triggers are disabled in single-user mode (see <xref
    linkend="app-postgres"/>).  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
+   <xref linkend="guc-ignore-event-trigger"/>.
   </para>
  </refsect1>
 
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index a3bdc5db07..0afd254173 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,22 @@ stringify_adefprivs_objtype(ObjectType objtype)
 
 	return "???";				/* keep compiler quiet */
 }
+
+
+/*
+ * Checks whether the specified event is ignored by the ignore_event_trigger
+ * GUC or not. Currently, the GUC only supports ignoring all or nothing but
+ * that might change so the function takes an event to aid that.
+ */
+static bool
+ignore_event_trigger_check(EventTriggerEvent event)
+{
+	(void) event;			/* unused parameter */
+
+	if (ignore_event_trigger == IGNORE_EVENT_TRIGGER_ALL)
+		return true;
+
+	/* IGNORE_EVENT_TRIGGER_NONE */
+	return false;
+
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1bf14eec66..423671c14a 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -36,6 +36,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"
@@ -446,6 +447,12 @@ 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},
+	{NULL, 0, false}
+};
+
 /*
  * Options for enum values stored in other modules
  */
@@ -4714,6 +4721,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 043864597f..9801eb9377 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -701,6 +701,7 @@
 #xmlbinary = 'base64'
 #xmloption = 'content'
 #gin_pending_list_limit = 4MB
+#ignore_event_trigger = 0
 
 # - Locale and Formatting -
 
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 10091c3aaf..e8f188df23 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -29,6 +29,14 @@ typedef struct EventTriggerData
 	CommandTag	tag;
 } EventTriggerData;
 
+typedef enum ignore_event_trigger_events
+{
+	IGNORE_EVENT_TRIGGER_NONE,
+	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..747ffefaa6 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -614,3 +614,24 @@ 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();
+CREATE POLICY pguc ON event_trigger_test USING (FALSE);
+DROP POLICY pguc ON event_trigger_test;
+NOTICE:  DROP POLICY dropped policy
+SET ignore_event_trigger = 'all';
+CREATE POLICY pguc ON event_trigger_test USING (FALSE);
+DROP POLICY pguc ON event_trigger_test;
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index 1aeaddbe71..c4845a7e6b 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -471,3 +471,25 @@ 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();
+
+CREATE POLICY pguc ON event_trigger_test USING (FALSE);
+DROP POLICY pguc ON event_trigger_test;
+SET ignore_event_trigger = 'all';
+CREATE POLICY pguc ON event_trigger_test USING (FALSE);
+DROP POLICY pguc ON event_trigger_test;
-- 
2.32.1 (Apple Git-133)

