PL/Perl: Add event trigger support
authorPeter Eisentraut <peter_e@gmx.net>
Wed, 11 Dec 2013 13:11:59 +0000 (08:11 -0500)
committerPeter Eisentraut <peter_e@gmx.net>
Wed, 11 Dec 2013 13:11:59 +0000 (08:11 -0500)
From: Dimitri Fontaine <dimitri@2ndQuadrant.fr>

doc/src/sgml/plperl.sgml
src/pl/plperl/expected/plperl_trigger.out
src/pl/plperl/plperl.c
src/pl/plperl/sql/plperl_trigger.sql

index 10eac0e243cecc3cd0cd29c5608460e19e44fbbe..34663e475fe01edfc96ab2fd12f43b00b3006cd6 100644 (file)
@@ -1211,6 +1211,56 @@ CREATE TRIGGER test_valid_id_trig
   </para>
  </sect1>
 
+ <sect1 id="plperl-event-triggers">
+  <title>PL/Perl Event Triggers</title>
+
+  <para>
+   PL/Perl can be used to write event trigger functions.  In an event trigger
+   function, the hash reference <varname>$_TD</varname> contains information
+   about the current trigger event.  <varname>$_TD</> is a global variable,
+   which gets a separate local value for each invocation of the trigger.  The
+   fields of the <varname>$_TD</varname> hash reference are:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>$_TD-&gt;{event}</literal></term>
+     <listitem>
+      <para>
+       The name of the event the trigger is fired for.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>$_TD-&gt;{tag}</literal></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 is an example of an event trigger function, illustrating some of the
+   above:
+<programlisting>
+CREATE OR REPLACE FUNCTION perlsnitch() RETURNS event_trigger AS $$
+  elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
+$$ LANGUAGE plperl;
+
+CREATE EVENT TRIGGER perl_a_snitch
+    ON ddl_command_start
+    EXECUTE PROCEDURE perlsnitch();
+</programlisting>
+  </para>
+ </sect1>
+
  <sect1 id="plperl-under-the-hood">
   <title>PL/Perl Under the Hood</title>
 
index 181dcfa7aeb723a04d4b261c156af7fd138984c6..36ecb920958b9288d87d1a43541defbcf2d05c61 100644 (file)
@@ -309,3 +309,38 @@ $$ LANGUAGE plperl;
 SELECT direct_trigger();
 ERROR:  trigger functions can only be called as triggers
 CONTEXT:  compilation of PL/Perl function "direct_trigger"
+-- test plperl command triggers
+create or replace function perlsnitch() returns event_trigger language plperl as $$
+  elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
+$$;
+create event trigger perl_a_snitch on ddl_command_start
+   execute procedure perlsnitch();
+create event trigger perl_b_snitch on ddl_command_end
+   execute procedure perlsnitch();
+create or replace function foobar() returns int language sql as $$select 1;$$;
+NOTICE:  perlsnitch: ddl_command_start CREATE FUNCTION 
+CONTEXT:  PL/Perl function "perlsnitch"
+NOTICE:  perlsnitch: ddl_command_end CREATE FUNCTION 
+CONTEXT:  PL/Perl function "perlsnitch"
+alter function foobar() cost 77;
+NOTICE:  perlsnitch: ddl_command_start ALTER FUNCTION 
+CONTEXT:  PL/Perl function "perlsnitch"
+NOTICE:  perlsnitch: ddl_command_end ALTER FUNCTION 
+CONTEXT:  PL/Perl function "perlsnitch"
+drop function foobar();
+NOTICE:  perlsnitch: ddl_command_start DROP FUNCTION 
+CONTEXT:  PL/Perl function "perlsnitch"
+NOTICE:  perlsnitch: ddl_command_end DROP FUNCTION 
+CONTEXT:  PL/Perl function "perlsnitch"
+create table foo();
+NOTICE:  perlsnitch: ddl_command_start CREATE TABLE 
+CONTEXT:  PL/Perl function "perlsnitch"
+NOTICE:  perlsnitch: ddl_command_end CREATE TABLE 
+CONTEXT:  PL/Perl function "perlsnitch"
+drop table foo;
+NOTICE:  perlsnitch: ddl_command_start DROP TABLE 
+CONTEXT:  PL/Perl function "perlsnitch"
+NOTICE:  perlsnitch: ddl_command_end DROP TABLE 
+CONTEXT:  PL/Perl function "perlsnitch"
+drop event trigger perl_a_snitch;
+drop event trigger perl_b_snitch;
index de8cb0e04761558460c770aa57c745dd93c21ab6..4f5b92fa3affbf77b7308dc8ea88cfbb1ca84631 100644 (file)
@@ -21,6 +21,7 @@
 #include "catalog/pg_language.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 "funcapi.h"
@@ -254,10 +255,13 @@ static void set_interp_require(bool trusted);
 
 static Datum plperl_func_handler(PG_FUNCTION_ARGS);
 static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
+static void plperl_event_trigger_handler(PG_FUNCTION_ARGS);
 
 static void free_plperl_function(plperl_proc_desc *prodesc);
 
-static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger);
+static plperl_proc_desc *compile_plperl_function(Oid fn_oid,
+                                                bool is_trigger,
+                                                bool is_event_trigger);
 
 static SV  *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc);
 static SV  *plperl_hash_from_datum(Datum attr);
@@ -1610,6 +1614,23 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
 }
 
 
+/* Set up the arguments for an event trigger call. */
+static SV  *
+plperl_event_trigger_build_args(FunctionCallInfo fcinfo)
+{
+   EventTriggerData *tdata;
+   HV         *hv;
+
+   hv = newHV();
+
+   tdata = (EventTriggerData *) fcinfo->context;
+
+   hv_store_string(hv, "event", cstr2sv(tdata->event));
+   hv_store_string(hv, "tag", cstr2sv(tdata->tag));
+
+   return newRV_noinc((SV *) hv);
+}
+
 /* Set up the new tuple returned from a trigger. */
 
 static HeapTuple
@@ -1717,6 +1738,11 @@ plperl_call_handler(PG_FUNCTION_ARGS)
        current_call_data = &this_call_data;
        if (CALLED_AS_TRIGGER(fcinfo))
            retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
+       else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+       {
+           plperl_event_trigger_handler(fcinfo);
+           retval = (Datum) 0;
+       }
        else
            retval = plperl_func_handler(fcinfo);
    }
@@ -1853,7 +1879,8 @@ plperl_validator(PG_FUNCTION_ARGS)
    Oid        *argtypes;
    char      **argnames;
    char       *argmodes;
-   bool        istrigger = false;
+   bool        is_trigger = false;
+   bool        is_event_trigger = false;
    int         i;
 
    /* Get the new function's pg_proc entry */
@@ -1865,13 +1892,15 @@ plperl_validator(PG_FUNCTION_ARGS)
    functyptype = get_typtype(proc->prorettype);
 
    /* Disallow pseudotype result */
-   /* except for TRIGGER, RECORD, or VOID */
+   /* except for TRIGGER, EVTTRIGGER, RECORD, or VOID */
    if (functyptype == TYPTYPE_PSEUDO)
    {
        /* we assume OPAQUE with no arguments means a trigger */
        if (proc->prorettype == TRIGGEROID ||
            (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
-           istrigger = true;
+           is_trigger = true;
+       else if (proc->prorettype == EVTTRIGGEROID)
+           is_event_trigger = true;
        else if (proc->prorettype != RECORDOID &&
                 proc->prorettype != VOIDOID)
            ereport(ERROR,
@@ -1898,7 +1927,7 @@ plperl_validator(PG_FUNCTION_ARGS)
    /* Postpone body checks if !check_function_bodies */
    if (check_function_bodies)
    {
-       (void) compile_plperl_function(funcoid, istrigger);
+       (void) compile_plperl_function(funcoid, is_trigger, is_event_trigger);
    }
 
    /* the result of a validator is ignored */
@@ -2169,6 +2198,63 @@ plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo,
 }
 
 
+static void
+plperl_call_perl_event_trigger_func(plperl_proc_desc *desc,
+                                   FunctionCallInfo fcinfo,
+                                   SV *td)
+{
+   dSP;
+   SV         *retval,
+              *TDsv;
+   int         count;
+
+   ENTER;
+   SAVETMPS;
+
+   TDsv = get_sv("main::_TD", 0);
+   if (!TDsv)
+       elog(ERROR, "couldn't fetch $_TD");
+
+   save_item(TDsv);            /* local $_TD */
+   sv_setsv(TDsv, td);
+
+   PUSHMARK(sp);
+   PUTBACK;
+
+   /* Do NOT use G_KEEPERR here */
+   count = perl_call_sv(desc->reference, G_SCALAR | G_EVAL);
+
+   SPAGAIN;
+
+   if (count != 1)
+   {
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+       elog(ERROR, "didn't get a return item from trigger function");
+   }
+
+   if (SvTRUE(ERRSV))
+   {
+       (void) POPs;
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+       /* XXX need to find a way to assign an errcode here */
+       ereport(ERROR,
+               (errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV)))));
+   }
+
+   retval = newSVsv(POPs);
+   (void) retval;              /* silence compiler warning */
+
+   PUTBACK;
+   FREETMPS;
+   LEAVE;
+
+   return;
+}
+
 static Datum
 plperl_func_handler(PG_FUNCTION_ARGS)
 {
@@ -2181,7 +2267,7 @@ plperl_func_handler(PG_FUNCTION_ARGS)
    if (SPI_connect() != SPI_OK_CONNECT)
        elog(ERROR, "could not connect to SPI manager");
 
-   prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false);
+   prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false, false);
    current_call_data->prodesc = prodesc;
    increment_prodesc_refcount(prodesc);
 
@@ -2295,7 +2381,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
        elog(ERROR, "could not connect to SPI manager");
 
    /* Find or compile the function */
-   prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true);
+   prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true, false);
    current_call_data->prodesc = prodesc;
    increment_prodesc_refcount(prodesc);
 
@@ -2386,6 +2472,45 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
 }
 
 
+static void
+plperl_event_trigger_handler(PG_FUNCTION_ARGS)
+{
+   plperl_proc_desc *prodesc;
+   SV         *svTD;
+   ErrorContextCallback pl_error_context;
+
+   /* 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_plperl_function(fcinfo->flinfo->fn_oid, false, true);
+   current_call_data->prodesc = prodesc;
+   increment_prodesc_refcount(prodesc);
+
+   /* Set a callback for error reporting */
+   pl_error_context.callback = plperl_exec_callback;
+   pl_error_context.previous = error_context_stack;
+   pl_error_context.arg = prodesc->proname;
+   error_context_stack = &pl_error_context;
+
+   activate_interpreter(prodesc->interp);
+
+   svTD = plperl_event_trigger_build_args(fcinfo);
+   plperl_call_perl_event_trigger_func(prodesc, fcinfo, svTD);
+
+   if (SPI_finish() != SPI_OK_FINISH)
+       elog(ERROR, "SPI_finish() failed");
+
+   /* Restore the previous error callback */
+   error_context_stack = pl_error_context.previous;
+
+   SvREFCNT_dec(svTD);
+
+   return;
+}
+
+
 static bool
 validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup)
 {
@@ -2437,7 +2562,7 @@ free_plperl_function(plperl_proc_desc *prodesc)
 
 
 static plperl_proc_desc *
-compile_plperl_function(Oid fn_oid, bool is_trigger)
+compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
 {
    HeapTuple   procTup;
    Form_pg_proc procStruct;
@@ -2543,7 +2668,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
         * Get the required information for input conversion of the
         * return value.
         ************************************************************/
-       if (!is_trigger)
+       if (!is_trigger && !is_event_trigger)
        {
            typeTup =
                SearchSysCache1(TYPEOID,
@@ -2562,7 +2687,8 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                if (procStruct->prorettype == VOIDOID ||
                    procStruct->prorettype == RECORDOID)
                     /* okay */ ;
-               else if (procStruct->prorettype == TRIGGEROID)
+               else if (procStruct->prorettype == TRIGGEROID ||
+                        procStruct->prorettype == EVTTRIGGEROID)
                {
                    free_plperl_function(prodesc);
                    ereport(ERROR,
@@ -2598,7 +2724,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
         * Get the required information for output conversion
         * of all procedure arguments
         ************************************************************/
-       if (!is_trigger)
+       if (!is_trigger && !is_event_trigger)
        {
            prodesc->nargs = procStruct->pronargs;
            for (i = 0; i < prodesc->nargs; i++)
index c43b31ede0a91bded905daa25cb87995fd9d9fb0..a375b401ea2705344678b5e8b495bade7f3c2897 100644 (file)
@@ -169,3 +169,23 @@ CREATE FUNCTION direct_trigger() RETURNS trigger AS $$
 $$ LANGUAGE plperl;
 
 SELECT direct_trigger();
+
+-- test plperl command triggers
+create or replace function perlsnitch() returns event_trigger language plperl as $$
+  elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
+$$;
+
+create event trigger perl_a_snitch on ddl_command_start
+   execute procedure perlsnitch();
+create event trigger perl_b_snitch on ddl_command_end
+   execute procedure perlsnitch();
+
+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 perl_a_snitch;
+drop event trigger perl_b_snitch;