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; +