*** 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;