Extend plsample example to include a trigger handler.
Mark Wong and Konstantina Skovola, reviewed by Chapman Flack Discussion: https://postgr.es/m/Yd8Cz22eHi80XS30@workstation-mark-wong
This commit is contained in:
parent
9f8a050f68
commit
2f4d0d6799
@ -34,3 +34,84 @@ NOTICE: argument: 0; name: a1; value: {foo,bar,hoge}
|
||||
|
||||
(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: source text of function "my_trigger_func":
|
||||
if TD_event == "INSERT"
|
||||
return TD_NEW
|
||||
elseif TD_event == "UPDATE"
|
||||
return TD_NEW
|
||||
else
|
||||
return "OK"
|
||||
end
|
||||
|
||||
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: source text of function "my_trigger_func":
|
||||
if TD_event == "INSERT"
|
||||
return TD_NEW
|
||||
elseif TD_event == "UPDATE"
|
||||
return TD_NEW
|
||||
else
|
||||
return "OK"
|
||||
end
|
||||
|
||||
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: source text of function "my_trigger_func":
|
||||
if TD_event == "INSERT"
|
||||
return TD_NEW
|
||||
elseif TD_event == "UPDATE"
|
||||
return TD_NEW
|
||||
else
|
||||
return "OK"
|
||||
end
|
||||
|
||||
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: source text of function "my_trigger_func":
|
||||
if TD_event == "INSERT"
|
||||
return TD_NEW
|
||||
elseif TD_event == "UPDATE"
|
||||
return TD_NEW
|
||||
else
|
||||
return "OK"
|
||||
end
|
||||
|
||||
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
|
||||
|
@ -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.
|
||||
@ -38,6 +40,11 @@ plsample_call_handler(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Datum retval = (Datum) 0;
|
||||
|
||||
/*
|
||||
* Many languages will require cleanup that happens even in the event of
|
||||
* an error. That can happen in the PG_FINALLY block. If none is needed,
|
||||
* this PG_TRY construct can be omitted.
|
||||
*/
|
||||
PG_TRY();
|
||||
{
|
||||
/*
|
||||
@ -51,6 +58,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))
|
||||
{
|
||||
@ -58,6 +66,8 @@ plsample_call_handler(PG_FUNCTION_ARGS)
|
||||
* This function is called as an event trigger function, where
|
||||
* (EventTriggerData *) fcinfo->context includes the information
|
||||
* of the context.
|
||||
*
|
||||
* TODO: provide an example handler.
|
||||
*/
|
||||
}
|
||||
else
|
||||
@ -101,9 +111,9 @@ plsample_func_handler(PG_FUNCTION_ARGS)
|
||||
FmgrInfo result_in_func;
|
||||
int numargs;
|
||||
|
||||
/* Fetch the source text of the function. */
|
||||
pl_tuple = SearchSysCache(PROCOID,
|
||||
ObjectIdGetDatum(fcinfo->flinfo->fn_oid), 0, 0, 0);
|
||||
/* Fetch the function's pg_proc entry. */
|
||||
pl_tuple = SearchSysCache1(PROCOID,
|
||||
ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
|
||||
if (!HeapTupleIsValid(pl_tuple))
|
||||
elog(ERROR, "cache lookup failed for function %u",
|
||||
fcinfo->flinfo->fn_oid);
|
||||
@ -185,3 +195,160 @@ 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 function's pg_proc entry. */
|
||||
pl_tuple = SearchSysCache1(PROCOID,
|
||||
ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
|
||||
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));
|
||||
ereport(NOTICE,
|
||||
(errmsg("source text of function \"%s\": %s",
|
||||
proname, source)));
|
||||
|
||||
/*
|
||||
* We're done with the pg_proc tuple, so release it. (Note that the
|
||||
* "proname" and "source" strings are now standalone copies.)
|
||||
*/
|
||||
ReleaseSysCache(pl_tuple);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 values from the TriggerData struct to the
|
||||
* appropriate parameters.
|
||||
*
|
||||
* In this example we just print a lot of info via ereport.
|
||||
*/
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
ereport(NOTICE,
|
||||
(errmsg("trigger name: %s", trigdata->tg_trigger->tgname)));
|
||||
string = SPI_getrelname(trigdata->tg_relation);
|
||||
ereport(NOTICE, (errmsg("trigger relation: %s", string)));
|
||||
|
||||
string = SPI_getnspname(trigdata->tg_relation);
|
||||
ereport(NOTICE, (errmsg("trigger relation schema: %s", 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 (int i = 0; i < trigdata->tg_trigger->tgnargs; i++)
|
||||
ereport(NOTICE,
|
||||
(errmsg("trigger arg[%i]: %s", i,
|
||||
trigdata->tg_trigger->tgargs[i])));
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
/* Error cleanup code would go here */
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
if (SPI_finish() != SPI_OK_FINISH)
|
||||
elog(ERROR, "SPI_finish() failed");
|
||||
|
||||
return rettup;
|
||||
}
|
||||
|
@ -13,3 +13,26 @@ 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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user