diff options
| author | Robert Haas | 2012-07-20 15:38:47 +0000 |
|---|---|---|
| committer | Robert Haas | 2012-07-20 15:39:01 +0000 |
| commit | 3a0e4d36ebd7f477822d5bae41ba121a40d22ccc (patch) | |
| tree | 323cd89ebb88c51b796b86f17f6efa1db76a761e /src/pl | |
| parent | be86e3dd5b42c33387ae976c014e6276c9439f7f (diff) | |
Make new event trigger facility actually do something.
Commit 3855968f328918b6cd1401dd11d109d471a54d40 added syntax, pg_dump,
psql support, and documentation, but the triggers didn't actually fire.
With this commit, they now do. This is still a pretty basic facility
overall because event triggers do not get a whole lot of information
about what the user is trying to do unless you write them in C; and
there's still no option to fire them anywhere except at the very
beginning of the execution sequence, but it's better than nothing,
and a good building block for future work.
Along the way, add a regression test for ALTER LARGE OBJECT, since
testing of event triggers reveals that we haven't got one.
Dimitri Fontaine and Robert Haas
Diffstat (limited to 'src/pl')
| -rw-r--r-- | src/pl/plpgsql/src/pl_comp.c | 54 | ||||
| -rw-r--r-- | src/pl/plpgsql/src/pl_exec.c | 93 | ||||
| -rw-r--r-- | src/pl/plpgsql/src/pl_handler.c | 23 | ||||
| -rw-r--r-- | src/pl/plpgsql/src/plpgsql.h | 15 |
4 files changed, 171 insertions, 14 deletions
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 5d2f818dacb..0dc0e0b37ed 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -263,7 +263,8 @@ do_compile(FunctionCallInfo fcinfo, bool forValidator) { Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); - bool is_trigger = CALLED_AS_TRIGGER(fcinfo); + bool is_dml_trigger = CALLED_AS_TRIGGER(fcinfo); + bool is_event_trigger = CALLED_AS_EVENT_TRIGGER(fcinfo); Datum prosrcdatum; bool isnull; char *proc_source; @@ -345,12 +346,18 @@ do_compile(FunctionCallInfo fcinfo, function->fn_oid = fcinfo->flinfo->fn_oid; function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); function->fn_tid = procTup->t_self; - function->fn_is_trigger = is_trigger; function->fn_input_collation = fcinfo->fncollation; function->fn_cxt = func_cxt; function->out_param_varno = -1; /* set up for no OUT param */ function->resolve_option = plpgsql_variable_conflict; + if (is_dml_trigger) + function->fn_is_trigger = PLPGSQL_DML_TRIGGER; + else if (is_event_trigger) + function->fn_is_trigger = PLPGSQL_EVENT_TRIGGER; + else + function->fn_is_trigger = PLPGSQL_NOT_TRIGGER; + /* * Initialize the compiler, particularly the namespace stack. The * outermost namespace contains function parameters and other special @@ -367,9 +374,9 @@ do_compile(FunctionCallInfo fcinfo, sizeof(PLpgSQL_datum *) * datums_alloc); datums_last = 0; - switch (is_trigger) + switch (function->fn_is_trigger) { - case false: + case PLPGSQL_NOT_TRIGGER: /* * Fetch info about the procedure's parameters. Allocations aren't @@ -529,7 +536,7 @@ do_compile(FunctionCallInfo fcinfo, if (rettypeid == VOIDOID || rettypeid == RECORDOID) /* okay */ ; - else if (rettypeid == TRIGGEROID) + else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called as triggers"))); @@ -568,7 +575,7 @@ do_compile(FunctionCallInfo fcinfo, ReleaseSysCache(typeTup); break; - case true: + case PLPGSQL_DML_TRIGGER: /* Trigger procedure's return type is unknown yet */ function->fn_rettype = InvalidOid; function->fn_retbyval = false; @@ -672,8 +679,39 @@ do_compile(FunctionCallInfo fcinfo, break; + case PLPGSQL_EVENT_TRIGGER: + function->fn_rettype = VOIDOID; + function->fn_retbyval = false; + function->fn_retistuple = true; + function->fn_retset = false; + + /* shouldn't be any declared arguments */ + if (procStruct->pronargs != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("event trigger functions cannot have declared arguments"))); + + /* Add the variable tg_event */ + var = plpgsql_build_variable("tg_event", 0, + plpgsql_build_datatype(TEXTOID, + -1, + function->fn_input_collation), + true); + function->tg_event_varno = var->dno; + + /* Add the variable tg_tag */ + var = plpgsql_build_variable("tg_tag", 0, + plpgsql_build_datatype(TEXTOID, + -1, + function->fn_input_collation), + true); + function->tg_tag_varno = var->dno; + + break; + default: - elog(ERROR, "unrecognized function typecode: %d", (int) is_trigger); + elog(ERROR, "unrecognized function typecode: %d", + (int) function->fn_is_trigger); break; } @@ -803,7 +841,7 @@ plpgsql_compile_inline(char *proc_source) compile_tmp_cxt = MemoryContextSwitchTo(func_cxt); function->fn_signature = pstrdup(func_name); - function->fn_is_trigger = false; + function->fn_is_trigger = PLPGSQL_NOT_TRIGGER; function->fn_input_collation = InvalidOid; function->fn_cxt = func_cxt; function->out_param_varno = -1; /* set up for no OUT param */ diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 11a56c9a8f1..8b649a4e5dc 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -773,6 +773,99 @@ plpgsql_exec_trigger(PLpgSQL_function *func, return rettup; } +void +plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata) +{ + PLpgSQL_execstate estate; + ErrorContextCallback plerrcontext; + int i; + int rc; + PLpgSQL_var *var; + + /* + * Setup the execution state + */ + plpgsql_estate_setup(&estate, func, NULL); + + /* + * Setup error traceback support for ereport() + */ + plerrcontext.callback = plpgsql_exec_error_callback; + plerrcontext.arg = &estate; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + /* + * Make local execution copies of all the datums + */ + estate.err_text = gettext_noop("during initialization of execution state"); + for (i = 0; i < estate.ndatums; i++) + estate.datums[i] = copy_plpgsql_datum(func->datums[i]); + + /* + * Assign the special tg_ variables + */ + var = (PLpgSQL_var *) (estate.datums[func->tg_event_varno]); + var->value = CStringGetTextDatum(trigdata->event); + var->isnull = false; + var->freeval = true; + + var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]); + var->value = CStringGetTextDatum(trigdata->tag); + var->isnull = false; + var->freeval = true; + + /* + * Let the instrumentation plugin peek at this function + */ + if (*plugin_ptr && (*plugin_ptr)->func_beg) + ((*plugin_ptr)->func_beg) (&estate, func); + + /* + * Now call the toplevel block of statements + */ + estate.err_text = NULL; + estate.err_stmt = (PLpgSQL_stmt *) (func->action); + rc = exec_stmt_block(&estate, func->action); + if (rc != PLPGSQL_RC_RETURN) + { + estate.err_stmt = NULL; + estate.err_text = NULL; + + /* + * Provide a more helpful message if a CONTINUE or RAISE has been used + * outside the context it can work in. + */ + if (rc == PLPGSQL_RC_CONTINUE) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("CONTINUE cannot be used outside a loop"))); + else + ereport(ERROR, + (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT), + errmsg("control reached end of trigger procedure without RETURN"))); + } + + estate.err_stmt = NULL; + estate.err_text = gettext_noop("during function exit"); + + /* + * Let the instrumentation plugin peek at this function + */ + if (*plugin_ptr && (*plugin_ptr)->func_end) + ((*plugin_ptr)->func_end) (&estate, func); + + /* Clean up any leftover temporary memory */ + plpgsql_destroy_econtext(&estate); + exec_eval_cleanup(&estate); + + /* + * Pop the error context stack + */ + error_context_stack = plerrcontext.previous; + + return; +} /* * error context callback to let us supply a call-stack traceback diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 022ec3f334c..2adf166164e 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -91,7 +91,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS) { PLpgSQL_function *func; PLpgSQL_execstate *save_cur_estate; - Datum retval; + Datum retval = 0; /* make compiler happy */ int rc; /* @@ -118,6 +118,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS) if (CALLED_AS_TRIGGER(fcinfo)) retval = PointerGetDatum(plpgsql_exec_trigger(func, (TriggerData *) fcinfo->context)); + else if (CALLED_AS_EVENT_TRIGGER(fcinfo)) + plpgsql_exec_event_trigger(func, + (EventTriggerData *) fcinfo->context); else retval = plpgsql_exec_function(func, fcinfo); } @@ -224,7 +227,8 @@ plpgsql_validator(PG_FUNCTION_ARGS) Oid *argtypes; char **argnames; char *argmodes; - bool istrigger = false; + bool is_dml_trigger = false; + bool is_event_trigger = false; int i; /* Get the new function's pg_proc entry */ @@ -242,7 +246,9 @@ plpgsql_validator(PG_FUNCTION_ARGS) /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID || (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) - istrigger = true; + is_dml_trigger = true; + else if (proc->prorettype == EVTTRIGGEROID) + is_event_trigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && !IsPolymorphicType(proc->prorettype)) @@ -273,7 +279,6 @@ plpgsql_validator(PG_FUNCTION_ARGS) { FunctionCallInfoData fake_fcinfo; FmgrInfo flinfo; - TriggerData trigdata; int rc; /* @@ -291,12 +296,20 @@ plpgsql_validator(PG_FUNCTION_ARGS) fake_fcinfo.flinfo = &flinfo; flinfo.fn_oid = funcoid; flinfo.fn_mcxt = CurrentMemoryContext; - if (istrigger) + if (is_dml_trigger) { + TriggerData trigdata; MemSet(&trigdata, 0, sizeof(trigdata)); trigdata.type = T_TriggerData; fake_fcinfo.context = (Node *) &trigdata; } + else if (is_event_trigger) + { + EventTriggerData trigdata; + MemSet(&trigdata, 0, sizeof(trigdata)); + trigdata.type = T_EventTriggerData; + fake_fcinfo.context = (Node *) &trigdata; + } /* Test-compile the function */ plpgsql_compile(&fake_fcinfo, true); diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index b63f336825d..dcf80743b88 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -19,6 +19,7 @@ #include "postgres.h" #include "access/xact.h" +#include "commands/event_trigger.h" #include "commands/trigger.h" #include "executor/spi.h" @@ -675,6 +676,12 @@ typedef struct PLpgSQL_func_hashkey Oid argtypes[FUNC_MAX_ARGS]; } PLpgSQL_func_hashkey; +typedef enum PLpgSQL_trigtype +{ + PLPGSQL_DML_TRIGGER, + PLPGSQL_EVENT_TRIGGER, + PLPGSQL_NOT_TRIGGER +} PLpgSQL_trigtype; typedef struct PLpgSQL_function { /* Complete compiled function */ @@ -682,7 +689,7 @@ typedef struct PLpgSQL_function Oid fn_oid; TransactionId fn_xmin; ItemPointerData fn_tid; - bool fn_is_trigger; + PLpgSQL_trigtype fn_is_trigger; Oid fn_input_collation; PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */ MemoryContext fn_cxt; @@ -713,6 +720,10 @@ typedef struct PLpgSQL_function int tg_nargs_varno; int tg_argv_varno; + /* for event triggers */ + int tg_event_varno; + int tg_tag_varno; + PLpgSQL_resolve_option resolve_option; int ndatums; @@ -920,6 +931,8 @@ extern Datum plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo); extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func, TriggerData *trigdata); +extern void plpgsql_exec_event_trigger(PLpgSQL_function *func, + EventTriggerData *trigdata); extern void plpgsql_xact_cb(XactEvent event, void *arg); extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg); |
