Add GUC for temporarily disabling event triggers
authorDaniel Gustafsson <dgustafsson@postgresql.org>
Mon, 25 Sep 2023 10:41:49 +0000 (12:41 +0200)
committerDaniel Gustafsson <dgustafsson@postgresql.org>
Mon, 25 Sep 2023 10:41:49 +0000 (12:41 +0200)
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
(or even recommended) 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 <yuzhihong@gmail.com>
Reviewed-by: Mikhail Gribkov <youzhick@gmail.com>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Discussion: https://postgr.es/m/9140106E-F9BF-4D85-8FC8-F2D3C094A6D9@yesql.se
Discussion: https://postgr.es/m/0d46d29f-4558-3af9-9c85-7774e14a7709@postgrespro.ru

doc/src/sgml/config.sgml
doc/src/sgml/ref/create_event_trigger.sgml
src/backend/commands/event_trigger.c
src/backend/utils/misc/guc_tables.c
src/backend/utils/misc/postgresql.conf.sample
src/include/commands/event_trigger.h
src/test/regress/expected/event_trigger.out
src/test/regress/sql/event_trigger.sql

index 6bc1b215db99dc53a16403061cccf46cb63549f1..d42b7d63ee692ffc88f84692fe379c8c584fa9ce 100644 (file)
@@ -9425,6 +9425,25 @@ SET XML OPTION { DOCUMENT | CONTENT };
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-event-triggers" xreflabel="event_triggers">
+      <term><varname>event_triggers</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>event_triggers</varname></primary>
+       <secondary>configuration parameter</secondary>
+     </indexterm>
+     </term>
+     <listitem>
+      <para>
+       Allow temporarily disabling execution of event triggers in order to
+       troubleshoot and repair faulty event triggers. All event triggers will
+       be disabled by setting it to <literal>false</literal>. Setting the value
+       to <literal>true</literal> allows all event triggers to fire, this
+       is the default value. 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">
index 22c8119198279ca0a8f094b2a7be20713e99dbe6..ef12cfa20d41109f484056d15f0197cd0e661360 100644 (file)
@@ -121,9 +121,12 @@ CREATE EVENT TRIGGER <replaceable class="parameter">name</replaceable>
 
   <para>
    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.
+   linkend="app-postgres"/>) as well as when
+   <xref linkend="guc-event-triggers"/> is set to <literal>false</literal>.
+   If an erroneous event trigger disables the database so much that you can't
+   even drop the trigger, restart with <xref linkend="guc-event-triggers"/>
+   set to <literal>false</literal> to temporarily disable event triggers, or
+   in single-user mode, and you'll be able to do that.
   </para>
  </refsect1>
 
index d4b00d1a828a424398ec3200d89b059627061232..bd812e42d943bb51dec92136732434adad48d32b 100644 (file)
@@ -72,6 +72,9 @@ typedef struct EventTriggerQueryState
 
 static EventTriggerQueryState *currentEventTriggerState = NULL;
 
+/* GUC parameter */
+bool       event_triggers = true;
+
 /* Support for dropped objects */
 typedef struct SQLDropObject
 {
@@ -657,8 +660,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 || !event_triggers)
        return;
 
    runlist = EventTriggerCommonSetup(parsetree,
@@ -692,9 +698,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 || !event_triggers)
        return;
 
    /*
@@ -740,9 +746,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 || !event_triggers)
        return;
 
    /*
@@ -811,9 +817,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 || !event_triggers)
        return;
 
    /*
index bdb26e2b77d6924dc8fd22572c0d3b35ce7ca994..16ec6c5ef02904a8c1a4588d80d1bc0db922315d 100644 (file)
@@ -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"
@@ -2000,6 +2001,16 @@ struct config_bool ConfigureNamesBool[] =
        NULL, NULL, NULL
    },
 
+   {
+       {"event_triggers", PGC_SUSET, CLIENT_CONN_STATEMENT,
+           gettext_noop("Enables event triggers."),
+           gettext_noop("When enabled, event triggers will fire for all applicable statements."),
+       },
+       &event_triggers,
+       true,
+       NULL, NULL, NULL
+   },
+
    /* End-of-list marker */
    {
        {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
index 6bb39d39badf4c283e227465238456483e8ba330..d08d55c3fe49ba986e5376bc280b71a843ae48ac 100644 (file)
 #xmloption = 'content'
 #gin_pending_list_limit = 4MB
 #createrole_self_grant = ''        # set and/or inherit
+#event_triggers = on
 
 # - Locale and Formatting -
 
index 5ed6ece555d05e029b4c8811d485a456035a1e63..1c925dbf257007af75283b7585b278a0fba50449 100644 (file)
@@ -29,6 +29,8 @@ typedef struct EventTriggerData
    CommandTag  tag;
 } EventTriggerData;
 
+extern PGDLLIMPORT bool event_triggers;
+
 #define AT_REWRITE_ALTER_PERSISTENCE   0x01
 #define AT_REWRITE_DEFAULT_VAL         0x02
 #define AT_REWRITE_COLUMN_REWRITE      0x04
index 2c8a6b22121089ce1824b8cab563114337a12efb..0b87a42d0a9ac6ba17fd86ad926fc33001a22cc7 100644 (file)
@@ -616,3 +616,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();
+SET event_triggers = 'on';
+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 event_triggers = 'off';
+DROP POLICY pguc ON event_trigger_test;
index 1aeaddbe7154ba09433b7c4538d9d4f28b67429c..6f0933b9e88aa53ef0d6eb10a1b0a8a4c5b03ba6 100644 (file)
@@ -471,3 +471,27 @@ 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 event_triggers = 'on';
+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 event_triggers = 'off';
+DROP POLICY pguc ON event_trigger_test;