|
|
|
@ -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; |
|
|
|
|
} |
|
|
|
|