]> granicus.if.org Git - postgresql/commitdiff
PL/Tcl: Add event trigger support
authorPeter Eisentraut <peter_e@gmx.net>
Sun, 24 Nov 2013 02:32:00 +0000 (21:32 -0500)
committerPeter Eisentraut <peter_e@gmx.net>
Sun, 24 Nov 2013 02:32:00 +0000 (21:32 -0500)
From: Dimitri Fontaine <dimitri@2ndQuadrant.fr>

doc/src/sgml/pltcl.sgml
src/pl/tcl/expected/pltcl_setup.out
src/pl/tcl/pltcl.c
src/pl/tcl/sql/pltcl_setup.sql

index 9f252e97caba6b8aa09f2034883813a7adeadb76..80400fad7b3dfbf5563a01387754797bef4a4783 100644 (file)
@@ -711,6 +711,65 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
     </para>
    </sect1>
 
+   <sect1 id="pltcl-event-trigger">
+    <title>Event Trigger Procedures in PL/Tcl</title>
+
+    <indexterm>
+     <primary>event trigger</primary>
+     <secondary>in PL/Tcl</secondary>
+    </indexterm>
+
+    <para>
+     Event trigger procedures can be written in PL/Tcl.
+     <productname>PostgreSQL</productname> requires that a procedure that is
+     to be called as an event trigger must be declared as a function with no
+     arguments and a return type of <literal>event_trigger</>.
+    </para>
+    <para>
+     The information from the trigger manager is passed to the procedure body
+     in the following variables:
+
+     <variablelist>
+
+      <varlistentry>
+       <term><varname>$TG_event</varname></term>
+       <listitem>
+        <para>
+         The name of the event the trigger is fired for.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><varname>$TG_tag</varname></term>
+       <listitem>
+        <para>
+         The command tag for which the trigger is fired.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </para>
+
+    <para>
+     The return value of the trigger procedure is ignored.
+    </para>
+
+    <para>
+     Here's a little example event trigger procedure that simply raises
+     a <literal>NOTICE</literal> message each time a supported command is
+     executed:
+
+<programlisting>
+CREATE OR REPLACE FUNCTION tclsnitch() RETURNS event_trigger AS $$
+  elog NOTICE "tclsnitch: $TG_event $TG_tag"
+$$ LANGUAGE pltcl;
+
+CREATE EVENT TRIGGER tcl_a_snitch ON ddl_command_start EXECUTE PROCEDURE tclsnitch();
+</programlisting>
+    </para>
+   </sect1>
+
    <sect1 id="pltcl-unknown">
        <title>Modules and the <function>unknown</> Command</title>
        <para>
index c4cdb26bde8faae5c879329ccf4e2cb338630e8c..4183c14b28a32e4c63cdb2bef3647c491eb01021 100644 (file)
@@ -519,3 +519,26 @@ select tcl_date_week(2001,10,24);
  42
 (1 row)
 
+-- test pltcl event triggers
+create or replace function tclsnitch() returns event_trigger language pltcl as $$
+  elog NOTICE "tclsnitch: $TG_event $TG_tag"
+$$;
+create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch();
+create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch();
+create or replace function foobar() returns int language sql as $$select 1;$$;
+NOTICE:  tclsnitch: ddl_command_start CREATE FUNCTION
+NOTICE:  tclsnitch: ddl_command_end CREATE FUNCTION
+alter function foobar() cost 77;
+NOTICE:  tclsnitch: ddl_command_start ALTER FUNCTION
+NOTICE:  tclsnitch: ddl_command_end ALTER FUNCTION
+drop function foobar();
+NOTICE:  tclsnitch: ddl_command_start DROP FUNCTION
+NOTICE:  tclsnitch: ddl_command_end DROP FUNCTION
+create table foo();
+NOTICE:  tclsnitch: ddl_command_start CREATE TABLE
+NOTICE:  tclsnitch: ddl_command_end CREATE TABLE
+drop table foo;
+NOTICE:  tclsnitch: ddl_command_start DROP TABLE
+NOTICE:  tclsnitch: ddl_command_end DROP TABLE
+drop event trigger tcl_a_snitch;
+drop event trigger tcl_b_snitch;
index c94d0d8075314d5039f1b40373ee0b139408e2cc..9b801b153a193a08ad8cc0f8641bf8c5ebd7b8e7 100644 (file)
@@ -27,6 +27,7 @@
 #include "access/xact.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/event_trigger.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
 #include "fmgr.h"
@@ -200,11 +201,13 @@ static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted);
 static Datum pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted);
 
 static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted);
+static void pltcl_event_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted);
 
 static void throw_tcl_error(Tcl_Interp *interp, const char *proname);
 
 static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid,
-                                          bool pltrusted);
+                                                                                          bool is_event_trigger,
+                                                                                          bool pltrusted);
 
 static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
                   int argc, CONST84 char *argv[]);
@@ -644,6 +647,12 @@ pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted)
                        pltcl_current_fcinfo = NULL;
                        retval = PointerGetDatum(pltcl_trigger_handler(fcinfo, pltrusted));
                }
+               else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+               {
+                       pltcl_current_fcinfo = NULL;
+                       pltcl_event_trigger_handler(fcinfo, pltrusted);
+                       retval = (Datum) 0;
+               }
                else
                {
                        pltcl_current_fcinfo = fcinfo;
@@ -685,7 +694,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted)
 
        /* Find or compile the function */
        prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid,
-                                                                        pltrusted);
+                                                                        false, pltrusted);
 
        pltcl_current_prodesc = prodesc;
 
@@ -844,6 +853,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
        /* Find or compile the function */
        prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
                                                                         RelationGetRelid(trigdata->tg_relation),
+                                                                        false, /* not an event trigger */
                                                                         pltrusted);
 
        pltcl_current_prodesc = prodesc;
@@ -1130,6 +1140,47 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
        return rettup;
 }
 
+/**********************************************************************
+ * pltcl_event_trigger_handler()       - Handler for event trigger calls
+ **********************************************************************/
+static void
+pltcl_event_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
+{
+       pltcl_proc_desc *prodesc;
+       Tcl_Interp *volatile interp;
+       EventTriggerData *tdata = (EventTriggerData *) fcinfo->context;
+       Tcl_DString tcl_cmd;
+       int                     tcl_rc;
+
+       /* Connect to SPI manager */
+       if (SPI_connect() != SPI_OK_CONNECT)
+               elog(ERROR, "could not connect to SPI manager");
+
+       /* Find or compile the function */
+       prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
+                                                                        InvalidOid, true, pltrusted);
+
+       pltcl_current_prodesc = prodesc;
+
+       interp = prodesc->interp_desc->interp;
+
+       /* Create the tcl command and call the internal proc */
+       Tcl_DStringInit(&tcl_cmd);
+       Tcl_DStringAppendElement(&tcl_cmd, prodesc->internal_proname);
+       Tcl_DStringAppendElement(&tcl_cmd, tdata->event);
+       Tcl_DStringAppendElement(&tcl_cmd, tdata->tag);
+
+       tcl_rc = Tcl_GlobalEval(interp, Tcl_DStringValue(&tcl_cmd));
+       Tcl_DStringFree(&tcl_cmd);
+
+       /* Check for errors reported by Tcl. */
+       if (tcl_rc != TCL_OK)
+               throw_tcl_error(interp, prodesc->user_proname);
+
+       if (SPI_finish() != SPI_OK_FINISH)
+               elog(ERROR, "SPI_finish() failed");
+}
+
 
 /**********************************************************************
  * throw_tcl_error     - ereport an error returned from the Tcl interpreter
@@ -1168,7 +1219,8 @@ throw_tcl_error(Tcl_Interp *interp, const char *proname)
  * (InvalidOid) when compiling a plain function.
  **********************************************************************/
 static pltcl_proc_desc *
-compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
+compile_pltcl_function(Oid fn_oid, Oid tgreloid,
+                                          bool is_event_trigger, bool pltrusted)
 {
        HeapTuple       procTup;
        Form_pg_proc procStruct;
@@ -1245,10 +1297,13 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
                 * "_trigger" when appropriate to ensure the normal and trigger
                 * cases are kept separate.
                 ************************************************************/
-               if (!is_trigger)
+               if (!is_trigger && !is_event_trigger)
                        snprintf(internal_proname, sizeof(internal_proname),
                                         "__PLTcl_proc_%u", fn_oid);
-               else
+               else if (is_event_trigger)
+                       snprintf(internal_proname, sizeof(internal_proname),
+                                        "__PLTcl_proc_%u_evttrigger", fn_oid);
+               else if (is_trigger)
                        snprintf(internal_proname, sizeof(internal_proname),
                                         "__PLTcl_proc_%u_trigger", fn_oid);
 
@@ -1286,7 +1341,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
                 * Get the required information for input conversion of the
                 * return value.
                 ************************************************************/
-               if (!is_trigger)
+               if (!is_trigger && !is_event_trigger)
                {
                        typeTup =
                                SearchSysCache1(TYPEOID,
@@ -1306,7 +1361,8 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
                        {
                                if (procStruct->prorettype == VOIDOID)
                                         /* okay */ ;
-                               else if (procStruct->prorettype == TRIGGEROID)
+                               else if (procStruct->prorettype == TRIGGEROID ||
+                                                procStruct->prorettype == EVTTRIGGEROID)
                                {
                                        free(prodesc->user_proname);
                                        free(prodesc->internal_proname);
@@ -1347,7 +1403,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
                 * Get the required information for output conversion
                 * of all procedure arguments
                 ************************************************************/
-               if (!is_trigger)
+               if (!is_trigger && !is_event_trigger)
                {
                        prodesc->nargs = procStruct->pronargs;
                        proc_internal_args[0] = '\0';
@@ -1397,12 +1453,17 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
                                ReleaseSysCache(typeTup);
                        }
                }
-               else
+               else if (is_trigger)
                {
                        /* trigger procedure has fixed args */
                        strcpy(proc_internal_args,
                                   "TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args");
                }
+               else if (is_event_trigger)
+               {
+                       /* event trigger procedure has fixed args */
+                       strcpy(proc_internal_args, "TG_event TG_tag");
+               }
 
                /************************************************************
                 * Create the tcl command to define the internal
@@ -1422,20 +1483,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
                Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1);
                Tcl_DStringAppend(&proc_internal_body, internal_proname, -1);
                Tcl_DStringAppend(&proc_internal_body, " GD\n", -1);
-               if (!is_trigger)
-               {
-                       for (i = 0; i < prodesc->nargs; i++)
-                       {
-                               if (prodesc->arg_is_rowtype[i])
-                               {
-                                       snprintf(buf, sizeof(buf),
-                                                        "array set %d $__PLTcl_Tup_%d\n",
-                                                        i + 1, i + 1);
-                                       Tcl_DStringAppend(&proc_internal_body, buf, -1);
-                               }
-                       }
-               }
-               else
+               if (is_trigger)
                {
                        Tcl_DStringAppend(&proc_internal_body,
                                                          "array set NEW $__PLTcl_Tup_NEW\n", -1);
@@ -1451,6 +1499,23 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
                                                          "}\n"
                                                          "unset i v\n\n", -1);
                }
+               else if (is_event_trigger)
+               {
+                       /* no argument support for event triggers */
+               }
+               else
+               {
+                       for (i = 0; i < prodesc->nargs; i++)
+                       {
+                               if (prodesc->arg_is_rowtype[i])
+                               {
+                                       snprintf(buf, sizeof(buf),
+                                                        "array set %d $__PLTcl_Tup_%d\n",
+                                                        i + 1, i + 1);
+                                       Tcl_DStringAppend(&proc_internal_body, buf, -1);
+                               }
+                       }
+               }
 
                /************************************************************
                 * Add user's function definition to proc body
index 0ac6669c6ef016beb72b20767bdc454a0da56f8d..84629963229e29657872ec8087b9efdc76f4bc56 100644 (file)
@@ -559,3 +559,21 @@ $$ language pltcl immutable;
 
 select tcl_date_week(2010,1,24);
 select tcl_date_week(2001,10,24);
+
+-- test pltcl event triggers
+create or replace function tclsnitch() returns event_trigger language pltcl as $$
+  elog NOTICE "tclsnitch: $TG_event $TG_tag"
+$$;
+
+create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch();
+create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch();
+
+create or replace function foobar() returns int language sql as $$select 1;$$;
+alter function foobar() cost 77;
+drop function foobar();
+
+create table foo();
+drop table foo;
+
+drop event trigger tcl_a_snitch;
+drop event trigger tcl_b_snitch;