summaryrefslogtreecommitdiff
path: root/src/pl
diff options
context:
space:
mode:
authorRobert Haas2012-07-20 15:38:47 +0000
committerRobert Haas2012-07-20 15:39:01 +0000
commit3a0e4d36ebd7f477822d5bae41ba121a40d22ccc (patch)
tree323cd89ebb88c51b796b86f17f6efa1db76a761e /src/pl
parentbe86e3dd5b42c33387ae976c014e6276c9439f7f (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.c54
-rw-r--r--src/pl/plpgsql/src/pl_exec.c93
-rw-r--r--src/pl/plpgsql/src/pl_handler.c23
-rw-r--r--src/pl/plpgsql/src/plpgsql.h15
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);