*** a/doc/src/sgml/plpython.sgml
--- b/doc/src/sgml/plpython.sgml
***************
*** 880,885 **** $$ LANGUAGE plpythonu;
--- 880,918 ----
+
+ Event Trigger Functions
+
+
+ event trigger
+ in PL/Python
+
+
+
+ When a function is used as an event trigger, the dictionary
+ TD contains event-trigger-related values:
+
+
+ TD["event"]>
+
+
+ The name of the event the trigger is fired for.
+
+
+
+
+
+ TD["tag"]>
+
+
+ The command tag for which the trigger is fired.
+
+
+
+
+
+
+
Database Access
*** a/src/pl/plpython/expected/plpython_trigger.out
--- b/src/pl/plpython/expected/plpython_trigger.out
***************
*** 634,636 **** SELECT * FROM b;
--- 634,663 ----
1234
(1 row)
+ create or replace function pysnitch() returns command_trigger language plpythonu as $$
+ plpy.notice(" pysnitch: %s %s %s.%s [%s]" %
+ (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"]));
+ $$;
+ create command trigger py_a_snitch after any command execute procedure pysnitch();
+ create command trigger py_b_snitch before any command execute procedure pysnitch();
+ create or replace function foobar() returns int language sql as $$select 1;$$;
+ ERROR: TypeError: not enough arguments for format string
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "pysnitch", line 3, in
+ (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"]));
+ PL/Python function "pysnitch"
+ alter function foobar() cost 77;
+ ERROR: function foobar() does not exist
+ drop function foobar();
+ ERROR: function foobar() does not exist
+ create table foo();
+ ERROR: TypeError: not enough arguments for format string
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "pysnitch", line 3, in
+ (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"]));
+ PL/Python function "pysnitch"
+ drop table foo;
+ ERROR: table "foo" does not exist
+ drop command trigger py_a_snitch;
+ drop command trigger py_b_snitch;
+ >>>>>>> Add regression tests for command triggers in pltcl, plpython and plperl.
*** a/src/pl/plpython/plpy_exec.c
--- b/src/pl/plpython/plpy_exec.c
***************
*** 9,14 ****
--- 9,15 ----
#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/pg_type.h"
+ #include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "funcapi.h"
***************
*** 332,337 **** PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
--- 333,389 ----
return rv;
}
+ /*
+ * Event trigger handler
+ */
+ void
+ PLy_exec_event_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
+ {
+ EventTriggerData *tdata;
+ PyObject *volatile pltdata = NULL;
+
+ Assert(CALLED_AS_EVENT_TRIGGER(fcinfo));
+
+ tdata = (EventTriggerData *) fcinfo->context;
+
+ PG_TRY();
+ {
+ /* build event trigger args */
+ PyObject *pltevent, *plttag;
+
+ pltdata = PyDict_New();
+ if (!pltdata)
+ PLy_elog(ERROR, "could not create new dictionary while building command trigger arguments");
+
+ pltevent = PyString_FromString(tdata->event);
+ PyDict_SetItemString(pltdata, "event", pltevent);
+ Py_DECREF(pltevent);
+
+ plttag = PyString_FromString(tdata->tag);
+ PyDict_SetItemString(pltdata, "tag", plttag);
+ Py_DECREF(plttag);
+
+ /* now call the procedure */
+ PLy_procedure_call(proc, "TD", pltdata);
+
+ /*
+ * Disconnect from SPI manager
+ */
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed");
+
+ Py_XDECREF(pltdata);
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(pltdata);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return;
+ }
+
/* helper functions for Python code execution */
static PyObject *
*** a/src/pl/plpython/plpy_exec.h
--- b/src/pl/plpython/plpy_exec.h
***************
*** 9,13 ****
--- 9,14 ----
extern Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc);
extern HeapTuple PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc);
+ extern void PLy_exec_event_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc);
#endif /* PLPY_EXEC_H */
*** a/src/pl/plpython/plpy_main.c
--- b/src/pl/plpython/plpy_main.c
***************
*** 9,14 ****
--- 9,15 ----
#include "access/htup_details.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+ #include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "miscadmin.h"
***************
*** 65,70 **** PG_FUNCTION_INFO_V1(plpython2_inline_handler);
--- 66,72 ----
static bool PLy_procedure_is_trigger(Form_pg_proc procStruct);
+ static bool PLy_procedure_is_event_trigger(Form_pg_proc procStruct);
static void plpython_error_callback(void *arg);
static void plpython_inline_error_callback(void *arg);
static void PLy_init_interp(void);
***************
*** 158,164 **** plpython_validator(PG_FUNCTION_ARGS)
Oid funcoid = PG_GETARG_OID(0);
HeapTuple tuple;
Form_pg_proc procStruct;
! bool is_trigger;
if (!check_function_bodies)
{
--- 160,166 ----
Oid funcoid = PG_GETARG_OID(0);
HeapTuple tuple;
Form_pg_proc procStruct;
! bool is_dml_trigger, is_evt_trigger;
if (!check_function_bodies)
{
***************
*** 171,182 **** plpython_validator(PG_FUNCTION_ARGS)
elog(ERROR, "cache lookup failed for function %u", funcoid);
procStruct = (Form_pg_proc) GETSTRUCT(tuple);
! is_trigger = PLy_procedure_is_trigger(procStruct);
ReleaseSysCache(tuple);
/* We can't validate triggers against any particular table ... */
! PLy_procedure_get(funcoid, InvalidOid, is_trigger);
PG_RETURN_VOID();
}
--- 173,185 ----
elog(ERROR, "cache lookup failed for function %u", funcoid);
procStruct = (Form_pg_proc) GETSTRUCT(tuple);
! is_dml_trigger = PLy_procedure_is_trigger(procStruct);
! is_evt_trigger = PLy_procedure_is_event_trigger(procStruct);
ReleaseSysCache(tuple);
/* We can't validate triggers against any particular table ... */
! PLy_procedure_get(funcoid, InvalidOid, is_dml_trigger, is_evt_trigger);
PG_RETURN_VOID();
}
***************
*** 225,238 **** plpython_call_handler(PG_FUNCTION_ARGS)
Relation tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
HeapTuple trv;
! proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), true);
exec_ctx->curr_proc = proc;
trv = PLy_exec_trigger(fcinfo, proc);
retval = PointerGetDatum(trv);
}
else
{
! proc = PLy_procedure_get(funcoid, InvalidOid, false);
exec_ctx->curr_proc = proc;
retval = PLy_exec_function(fcinfo, proc);
}
--- 228,248 ----
Relation tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
HeapTuple trv;
! proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel),
! true, false);
exec_ctx->curr_proc = proc;
trv = PLy_exec_trigger(fcinfo, proc);
retval = PointerGetDatum(trv);
}
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ {
+ proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, InvalidOid, false, true);
+ exec_ctx->curr_proc = proc;
+ PLy_exec_event_trigger(fcinfo, proc);
+ }
else
{
! proc = PLy_procedure_get(funcoid, InvalidOid, false, false);
exec_ctx->curr_proc = proc;
retval = PLy_exec_function(fcinfo, proc);
}
***************
*** 343,348 **** PLy_procedure_is_trigger(Form_pg_proc procStruct)
--- 353,363 ----
procStruct->pronargs == 0));
}
+ static bool PLy_procedure_is_event_trigger(Form_pg_proc procStruct)
+ {
+ return (procStruct->prorettype == EVENTTRIGGEROID);
+ }
+
static void
plpython_error_callback(void *arg)
{
*** a/src/pl/plpython/plpy_procedure.c
--- b/src/pl/plpython/plpy_procedure.c
***************
*** 25,31 ****
static HTAB *PLy_procedure_cache = NULL;
! static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
static char *PLy_procedure_munge_source(const char *name, const char *src);
--- 25,32 ----
static HTAB *PLy_procedure_cache = NULL;
! static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid,
! bool is_dml_trigger, bool is_cmd_trigger);
static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
static char *PLy_procedure_munge_source(const char *name, const char *src);
***************
*** 72,80 **** PLy_procedure_name(PLyProcedure *proc)
* be used with, so no sensible fn_rel can be passed.
*/
PLyProcedure *
! PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger)
{
! bool use_cache = !(is_trigger && fn_rel == InvalidOid);
HeapTuple procTup;
PLyProcedureKey key;
PLyProcedureEntry *volatile entry = NULL;
--- 73,82 ----
* be used with, so no sensible fn_rel can be passed.
*/
PLyProcedure *
! PLy_procedure_get(Oid fn_oid, Oid fn_rel,
! bool is_dml_trigger, bool is_evt_trigger)
{
! bool use_cache = !(is_dml_trigger && fn_rel == InvalidOid);
HeapTuple procTup;
PLyProcedureKey key;
PLyProcedureEntry *volatile entry = NULL;
***************
*** 102,109 **** PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger)
{
if (!found)
{
! /* Haven't found it, create a new procedure */
! proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
if (use_cache)
entry->proc = proc;
}
--- 104,112 ----
{
if (!found)
{
! /* Haven't found it, create a new cache entry */
! entry->proc = PLy_procedure_create(procTup, fn_oid,
! is_dml_trigger, is_evt_trigger);
if (use_cache)
entry->proc = proc;
}
***************
*** 112,118 **** PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger)
/* Found it, but it's invalid, free and reuse the cache entry */
PLy_procedure_delete(proc);
PLy_free(proc);
! proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
entry->proc = proc;
}
/* Found it and it's valid, it's fine to use it */
--- 115,122 ----
/* Found it, but it's invalid, free and reuse the cache entry */
PLy_procedure_delete(proc);
PLy_free(proc);
! proc = PLy_procedure_create(procTup, fn_oid,
! is_dml_trigger, is_evt_trigger);
entry->proc = proc;
}
/* Found it and it's valid, it's fine to use it */
***************
*** 135,141 **** PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger)
* Create a new PLyProcedure structure
*/
static PLyProcedure *
! PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
{
char procName[NAMEDATALEN + 256];
Form_pg_proc procStruct;
--- 139,146 ----
* Create a new PLyProcedure structure
*/
static PLyProcedure *
! PLy_procedure_create(HeapTuple procTup, Oid fn_oid,
! bool is_dml_trigger, bool is_cmd_trigger)
{
char procName[NAMEDATALEN + 256];
Form_pg_proc procStruct;
***************
*** 179,185 **** PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
* get information required for output conversion of the return value,
* but only if this isn't a trigger.
*/
! if (!is_trigger)
{
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
--- 184,190 ----
* get information required for output conversion of the return value,
* but only if this isn't a trigger.
*/
! if (!is_dml_trigger && !is_cmd_trigger)
{
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
*** a/src/pl/plpython/plpy_procedure.h
--- b/src/pl/plpython/plpy_procedure.h
***************
*** 48,54 **** typedef struct PLyProcedureEntry
/* PLyProcedure manipulation */
extern char *PLy_procedure_name(PLyProcedure *proc);
! extern PLyProcedure *PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger);
extern void PLy_procedure_compile(PLyProcedure *proc, const char *src);
extern void PLy_procedure_delete(PLyProcedure *proc);
--- 48,55 ----
/* PLyProcedure manipulation */
extern char *PLy_procedure_name(PLyProcedure *proc);
! extern PLyProcedure *PLy_procedure_get(Oid fn_oid, Oid fn_rel,
! bool is_dml_trigger, bool is_evt_trigger);
extern void PLy_procedure_compile(PLyProcedure *proc, const char *src);
extern void PLy_procedure_delete(PLyProcedure *proc);
*** a/src/pl/plpython/sql/plpython_trigger.sql
--- b/src/pl/plpython/sql/plpython_trigger.sql
***************
*** 406,408 **** SELECT * FROM a;
--- 406,425 ----
DROP TABLE a;
INSERT INTO b DEFAULT VALUES;
SELECT * FROM b;
+ -- test plpython command triggers
+ create or replace function pysnitch() returns command_trigger language plpythonu as $$
+ plpy.notice(" pysnitch: %s %s" % (TD["event"], TD["tag"]));
+ $$;
+
+ create event trigger py_a_snitch on ddl_command_start execute procedure pysnitch();
+ create event trigger py_b_snitch on ddl_command_end execute procedure pysnitch();
+
+ create or replace function foobar() returns int language sql as $$select 1;$$;
+ alter function foobar() cost 77;
+ drop function foobar();
+
+ create table foo();
+ drop table foo;
+
+ drop event trigger py_a_snitch;
+ drop event trigger py_b_snitch;