On Thu, Mar 10, 2022 at 06:36:44PM -0500, Chapman Flack wrote:
> On 03/02/22 15:12, Mark Wong wrote:
> 
> > I've attached v2, which reduces the output:
> > 
> > * Removing the notices for the text body, and the "compile" message.
> > * Replaced the notice for "compile" message with a comment as a
> >   placeholder for where a compiling code or checking a cache may go.
> > * Reducing the number of rows inserted into the table, thus reducing
> >   the number of notice messages about which code path is taken.
> 
> I think the simplifying assumption of a simple interpreted language allows
> a lot more of this code to go away. I mean more or less that whole first
> PG_TRY...PG_END_TRY block, which could be replaced with a comment saying
> something like
> 
>   The source text may be augmented here, such as by wrapping it as the
>   body of a function in the target language, prefixing a parameter list
>   with names like TD_name, TD_relid, TD_table_name, TD_table_schema,
>   TD_event, TD_when, TD_level, TD_NEW, TD_OLD, and args, using whatever
>   types in the target language are convenient. The augmented text can be
>   cached in a longer-lived memory context, or, if the target language uses
>   a compilation step, that can be done here, caching the result of the
>   compilation.
> 
> That would leave only the later PG_TRY block where the function gets
> "executed". That could stay largely as is, but should probably also have
> a comment within it, something like
> 
>   Here the function (the possibly-augmented source text, or the result
>   of compilation if the target language uses such a step) should be
>   executed, after binding these values from the TriggerData struct to
>   the expected parameters.
> 
> That should make the example shorter and clearer, and preserve the output
> tested by the regression test. Trying to show much more than that involves
> assumptions about what the PL's target language syntax looks like, how its
> execution engine works and parameters are bound, and so on, and that is
> likely to just be distracting, IMV.

I think I've applied all of these suggestions and attached a new patch.

Regards,
Mark
diff --git a/src/test/modules/plsample/expected/plsample.out 
b/src/test/modules/plsample/expected/plsample.out
index a0c318b6df..a7912c7897 100644
--- a/src/test/modules/plsample/expected/plsample.out
+++ b/src/test/modules/plsample/expected/plsample.out
@@ -6,9 +6,6 @@ AS $$
   Example of source with text result.
 $$ LANGUAGE plsample;
 SELECT plsample_result_text(1.23, 'abc', '{4, 5, 6}');
-NOTICE:  source text of function "plsample_result_text": 
-  Example of source with text result.
-
 NOTICE:  argument: 0; name: a1; value: 1.23
 NOTICE:  argument: 1; name: a2; value: abc
 NOTICE:  argument: 2; name: a3; value: {4,5,6}
@@ -25,12 +22,57 @@ AS $$
   Example of source with void result.
 $$ LANGUAGE plsample;
 SELECT plsample_result_void('{foo, bar, hoge}');
-NOTICE:  source text of function "plsample_result_void": 
-  Example of source with void result.
-
 NOTICE:  argument: 0; name: a1; value: {foo,bar,hoge}
  plsample_result_void 
 ----------------------
  
 (1 row)
 
+CREATE FUNCTION my_trigger_func() RETURNS trigger AS $$
+if TD_event == "INSERT"
+    return TD_NEW
+elseif TD_event == "UPDATE"
+    return TD_NEW
+else
+    return "OK"
+end
+$$ language plsample;
+CREATE TABLE my_table (num integer, description text);
+CREATE TRIGGER my_trigger_func BEFORE INSERT OR UPDATE ON my_table
+       FOR EACH ROW EXECUTE FUNCTION my_trigger_func();
+CREATE TRIGGER my_trigger_func2 AFTER INSERT OR UPDATE ON my_table
+       FOR EACH ROW EXECUTE FUNCTION my_trigger_func(8);
+INSERT INTO my_table (num, description)
+VALUES (1, 'first');
+NOTICE:  trigger name: my_trigger_func
+NOTICE:  trigger relation: my_table
+NOTICE:  trigger relation schema: public
+NOTICE:  triggered by INSERT
+NOTICE:  triggered BEFORE
+NOTICE:  triggered per row
+NOTICE:  trigger name: my_trigger_func2
+NOTICE:  trigger relation: my_table
+NOTICE:  trigger relation schema: public
+NOTICE:  triggered by INSERT
+NOTICE:  triggered AFTER
+NOTICE:  triggered per row
+NOTICE:  trigger arg[0]: 8
+UPDATE my_table
+SET description = 'first, modified once'
+WHERE num = 1;
+NOTICE:  trigger name: my_trigger_func
+NOTICE:  trigger relation: my_table
+NOTICE:  trigger relation schema: public
+NOTICE:  triggered by UPDATE
+NOTICE:  triggered BEFORE
+NOTICE:  triggered per row
+NOTICE:  trigger name: my_trigger_func2
+NOTICE:  trigger relation: my_table
+NOTICE:  trigger relation schema: public
+NOTICE:  triggered by UPDATE
+NOTICE:  triggered AFTER
+NOTICE:  triggered per row
+NOTICE:  trigger arg[0]: 8
+DROP TRIGGER my_trigger_func ON my_table;
+DROP TRIGGER my_trigger_func2 ON my_table;
+DROP FUNCTION my_trigger_func;
diff --git a/src/test/modules/plsample/plsample.c 
b/src/test/modules/plsample/plsample.c
index 6fc33c728c..45c381a6cb 100644
--- a/src/test/modules/plsample/plsample.c
+++ b/src/test/modules/plsample/plsample.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "commands/event_trigger.h"
 #include "commands/trigger.h"
+#include "executor/spi.h"
 #include "funcapi.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -29,6 +30,7 @@ PG_MODULE_MAGIC;
 PG_FUNCTION_INFO_V1(plsample_call_handler);
 
 static Datum plsample_func_handler(PG_FUNCTION_ARGS);
+static HeapTuple plsample_trigger_handler(PG_FUNCTION_ARGS);
 
 /*
  * Handle function, procedure, and trigger calls.
@@ -51,6 +53,7 @@ plsample_call_handler(PG_FUNCTION_ARGS)
                         * (TriggerData *) fcinfo->context includes the 
information of the
                         * context.
                         */
+                       retval = 
PointerGetDatum(plsample_trigger_handler(fcinfo));
                }
                else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
                {
@@ -119,9 +122,6 @@ plsample_func_handler(PG_FUNCTION_ARGS)
                elog(ERROR, "could not find source text of function \"%s\"",
                         proname);
        source = DatumGetCString(DirectFunctionCall1(textout, ret));
-       ereport(NOTICE,
-                       (errmsg("source text of function \"%s\": %s",
-                                       proname, source)));
 
        /*
         * Allocate a context that will hold all the Postgres data for the
@@ -185,3 +185,156 @@ plsample_func_handler(PG_FUNCTION_ARGS)
        ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
        PG_RETURN_DATUM(ret);
 }
+
+/*
+ * plsample_trigger_handler
+ *
+ * Function called by the call handler for trigger execution.
+ */
+static HeapTuple
+plsample_trigger_handler(PG_FUNCTION_ARGS)
+{
+       TriggerData *trigdata = (TriggerData *) fcinfo->context;
+       char       *string;
+       volatile HeapTuple rettup;
+       HeapTuple       pl_tuple;
+       Datum           ret;
+       char       *source;
+       bool            isnull;
+       Form_pg_proc pl_struct;
+       char       *proname;
+       int                     rc PG_USED_FOR_ASSERTS_ONLY;
+
+       /* Make sure this is being called from a trigger. */
+       if (!CALLED_AS_TRIGGER(fcinfo))
+               elog(ERROR, "not called by trigger manager");
+
+       /* Connect to the SPI manager */
+       if (SPI_connect() != SPI_OK_CONNECT)
+               elog(ERROR, "could not connect to SPI manager");
+
+       rc = SPI_register_trigger_data(trigdata);
+       Assert(rc >= 0);
+
+       /* Fetch the source text of the function. */
+       pl_tuple = SearchSysCache(PROCOID,
+                                                         
ObjectIdGetDatum(fcinfo->flinfo->fn_oid), 0, 0, 0);
+       if (!HeapTupleIsValid(pl_tuple))
+               elog(ERROR, "cache lookup failed for function %u",
+                        fcinfo->flinfo->fn_oid);
+
+       /*
+        * Code Retrieval
+        *
+        * Extract and print the source text of the function.  This can be used 
as
+        * a base for the function validation and execution.
+        */
+       pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
+       proname = pstrdup(NameStr(pl_struct->proname));
+       ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
+       if (isnull)
+               elog(ERROR, "could not find source text of function \"%s\"",
+                        proname);
+       source = DatumGetCString(DirectFunctionCall1(textout, ret));
+
+
+       /*
+        * Code Augmentation
+        *
+        * The source text may be augmented here, such as by wrapping it as the
+        * body of a function in the target language, prefixing a parameter list
+        * with names like TD_name, TD_relid, TD_table_name, TD_table_schema,
+        * TD_event, TD_when, TD_level, TD_NEW, TD_OLD, and args, using whatever
+        * types in the target language are convenient. The augmented text can 
be
+        * cached in a longer-lived memory context, or, if the target language 
uses
+        * a compilation step, that can be done here, caching the result of the
+        * compilation.
+        */
+
+       pfree(source);
+       ReleaseSysCache(pl_tuple);
+
+       /*
+        * Code Execution
+        *
+        * Here the function (the possibly-augmented source text, or the result 
of
+        * compilation if the target language uses such a step) should be 
executed,
+        * after binding these values from the TriggerData struct to the 
expected
+        * parameters.
+        */
+
+       PG_TRY();
+       {
+               int                     i;
+
+               ereport(NOTICE,
+                               (errmsg("trigger name: %s", 
trigdata->tg_trigger->tgname)));
+               string = SPI_getrelname(trigdata->tg_relation);
+               ereport(NOTICE, (errmsg("trigger relation: %s", string)));
+               pfree(string);
+
+               string = SPI_getnspname(trigdata->tg_relation);
+               ereport(NOTICE, (errmsg("trigger relation schema: %s", 
string)));
+               pfree(string);
+
+               /* Example handling of different trigger aspects. */
+
+               if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+               {
+                       ereport(NOTICE, (errmsg("triggered by INSERT")));
+                       rettup = trigdata->tg_trigtuple;
+               }
+               else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+               {
+                       ereport(NOTICE, (errmsg("triggered by DELETE")));
+                       rettup = trigdata->tg_trigtuple;
+               }
+               else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+               {
+                       ereport(NOTICE, (errmsg("triggered by UPDATE")));
+                       rettup = trigdata->tg_trigtuple;
+               }
+               else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+               {
+                       ereport(NOTICE, (errmsg("triggered by TRUNCATE")));
+                       rettup = trigdata->tg_trigtuple;
+               }
+               else
+                       elog(ERROR, "unrecognized event: %u", 
trigdata->tg_event);
+
+               if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
+                       ereport(NOTICE, (errmsg("triggered BEFORE")));
+               else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
+                       ereport(NOTICE, (errmsg("triggered AFTER")));
+               else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
+                       ereport(NOTICE, (errmsg("triggered INSTEAD OF")));
+               else
+                       elog(ERROR, "unrecognized when: %u", 
trigdata->tg_event);
+
+               if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+                       ereport(NOTICE, (errmsg("triggered per row")));
+               else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
+                       ereport(NOTICE, (errmsg("triggered per statement")));
+               else
+                       elog(ERROR, "unrecognized level: %u", 
trigdata->tg_event);
+
+               /*
+                * Iterate through all of the trigger arguments, printing each 
input
+                * value.
+                */
+               for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
+                       ereport(NOTICE,
+                                       (errmsg("trigger arg[%i]: %s", i,
+                                                       
trigdata->tg_trigger->tgargs[i])));
+       }
+       PG_CATCH();
+       {
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
+
+       if (SPI_finish() != SPI_OK_FINISH)
+               elog(ERROR, "SPI_finish() failed");
+
+       return rettup;
+}
diff --git a/src/test/modules/plsample/sql/plsample.sql 
b/src/test/modules/plsample/sql/plsample.sql
index bf0fddac7f..ac06e99643 100644
--- a/src/test/modules/plsample/sql/plsample.sql
+++ b/src/test/modules/plsample/sql/plsample.sql
@@ -13,3 +13,31 @@ AS $$
   Example of source with void result.
 $$ LANGUAGE plsample;
 SELECT plsample_result_void('{foo, bar, hoge}');
+
+CREATE FUNCTION my_trigger_func() RETURNS trigger AS $$
+if TD_event == "INSERT"
+    return TD_NEW
+elseif TD_event == "UPDATE"
+    return TD_NEW
+else
+    return "OK"
+end
+$$ language plsample;
+
+CREATE TABLE my_table (num integer, description text);
+CREATE TRIGGER my_trigger_func BEFORE INSERT OR UPDATE ON my_table
+       FOR EACH ROW EXECUTE FUNCTION my_trigger_func();
+CREATE TRIGGER my_trigger_func2 AFTER INSERT OR UPDATE ON my_table
+       FOR EACH ROW EXECUTE FUNCTION my_trigger_func(8);
+
+INSERT INTO my_table (num, description)
+VALUES (1, 'first');
+
+UPDATE my_table
+SET description = 'first, modified once'
+WHERE num = 1;
+
+DROP TRIGGER my_trigger_func ON my_table;
+DROP TRIGGER my_trigger_func2 ON my_table;
+DROP FUNCTION my_trigger_func;
+

Reply via email to