diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index cb065bf5f88..9e8e7bbc3c7 100644 --- a/doc/src/sgml/plpython.sgml +++ b/doc/src/sgml/plpython.sgml @@ -662,6 +662,20 @@ $$ LANGUAGE plpython3u; in PL/Python + + PL/Python can be used to define trigger + functions on data changes or database events. + A trigger function is created with the CREATE FUNCTION + command, declaring it as a function with no arguments and a return type of + trigger (for data change triggers) or + event_trigger (for database event triggers). + Special dictionary named TD are automatically defined to + describe the condition that triggered the call. + + + + Triggers on Data Changes + When a function is used as a trigger, the dictionary TD contains trigger-related values: @@ -767,6 +781,74 @@ $$ LANGUAGE plpython3u; "MODIFY" to indicate you've modified the new row. Otherwise the return value is ignored. + + + + Event Trigger Functions + + + event trigger + in PL/Python + + + + PL/Python can be used to define + event triggers. + PostgreSQL requires that a function that + is to be called as an event trigger must be declared as a function with + no arguments and a return type of event_trigger. + + + + When a PL/Python function is called as an + event trigger, a special dictionary named TD is + automatically created. The TD keys and their associated + values are: + + + + TD["event"] text + + + event the trigger is fired for. + + + + + + TD["tag"] text + + + command tag for which the trigger is fired. + + + + + + + + shows an example of an + event trigger function in PL/Python. + + + + A <application>PL/Python</application> Event Trigger Function + + + This example trigger simply raises a NOTICE message + each time a supported command is executed. + + + +CREATE OR REPLACE FUNCTION pysnitch() RETURNS event_trigger AS $$ + plpy.notice("TD[event] => " + str(TD["event"]) + " ; TD[tag] => " + str(TD["tag"])); +$$ LANGUAGE plpython3u; + +CREATE EVENT TRIGGER pysnitch ON ddl_command_start EXECUTE FUNCTION pysnitch(); + + + + diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out index 64eab2fa3f4..efc1610de63 100644 --- a/src/pl/plpython/expected/plpython_trigger.out +++ b/src/pl/plpython/expected/plpython_trigger.out @@ -646,3 +646,28 @@ SELECT * FROM recursive_trigger_test; 1 | 2 (2 rows) +-- event triggers +CREATE OR REPLACE FUNCTION pysnitch() RETURNS event_trigger AS $$ + plpy.notice("TD[event] => " + str(TD["event"]) + " ; TD[tag] => " + str(TD["tag"])); +$$ LANGUAGE plpython3u; +CREATE EVENT TRIGGER python_a_snitch ON ddl_command_start + EXECUTE PROCEDURE pysnitch(); +CREATE EVENT TRIGGER python_b_snitch ON ddl_command_end + EXECUTE PROCEDURE pysnitch(); +CREATE OR REPLACE FUNCTION foobar() RETURNS int LANGUAGE sql AS $$SELECT 1;$$; +NOTICE: TD[event] => ddl_command_start ; TD[tag] => CREATE FUNCTION +NOTICE: TD[event] => ddl_command_end ; TD[tag] => CREATE FUNCTION +ALTER FUNCTION foobar() COST 77; +NOTICE: TD[event] => ddl_command_start ; TD[tag] => ALTER FUNCTION +NOTICE: TD[event] => ddl_command_end ; TD[tag] => ALTER FUNCTION +DROP FUNCTION foobar(); +NOTICE: TD[event] => ddl_command_start ; TD[tag] => DROP FUNCTION +NOTICE: TD[event] => ddl_command_end ; TD[tag] => DROP FUNCTION +CREATE TABLE foo(); +NOTICE: TD[event] => ddl_command_start ; TD[tag] => CREATE TABLE +NOTICE: TD[event] => ddl_command_end ; TD[tag] => CREATE TABLE +DROP TABLE foo; +NOTICE: TD[event] => ddl_command_start ; TD[tag] => DROP TABLE +NOTICE: TD[event] => ddl_command_end ; TD[tag] => DROP TABLE +DROP EVENT TRIGGER python_a_snitch; +DROP EVENT TRIGGER python_b_snitch; diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c index 28fbd443b98..1cfbf0caa73 100644 --- a/src/pl/plpython/plpy_exec.c +++ b/src/pl/plpython/plpy_exec.c @@ -9,6 +9,7 @@ #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" @@ -427,6 +428,45 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) return rv; } +/* event trigger subhandler */ +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(); + { + PyObject *pltevent, + *plttag; + + pltdata = PyDict_New(); + if (!pltdata) + PLy_elog(ERROR, NULL); + + pltevent = PLyUnicode_FromString(tdata->event); + PyDict_SetItemString(pltdata, "event", pltevent); + Py_DECREF(pltevent); + + plttag = PLyUnicode_FromString(GetCommandTagName(tdata->tag)); + PyDict_SetItemString(pltdata, "tag", plttag); + Py_DECREF(plttag); + + PLy_procedure_call(proc, "TD", pltdata); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish() failed"); + } + PG_FINALLY(); + { + Py_XDECREF(pltdata); + } + PG_END_TRY(); +} + /* helper functions for Python code execution */ static PyObject * @@ -509,7 +549,7 @@ PLy_function_save_args(PLyProcedure *proc) Py_XINCREF(result->args); /* If it's a trigger, also save "TD" */ - if (proc->is_trigger) + if (proc->is_trigger == PLPY_TRIGGER) { result->td = PyDict_GetItemString(proc->globals, "TD"); Py_XINCREF(result->td); diff --git a/src/pl/plpython/plpy_exec.h b/src/pl/plpython/plpy_exec.h index 68da1ffcb2e..f35eabbd8ee 100644 --- a/src/pl/plpython/plpy_exec.h +++ b/src/pl/plpython/plpy_exec.h @@ -9,5 +9,6 @@ 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 */ diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c index f36eadbadc6..17b27f3fbe5 100644 --- a/src/pl/plpython/plpy_main.c +++ b/src/pl/plpython/plpy_main.c @@ -9,6 +9,7 @@ #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" @@ -19,6 +20,7 @@ #include "plpy_procedure.h" #include "plpy_subxactobject.h" #include "plpy_util.h" +#include "utils/builtins.h" #include "utils/guc.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -38,7 +40,7 @@ PG_FUNCTION_INFO_V1(plpython3_call_handler); PG_FUNCTION_INFO_V1(plpython3_inline_handler); -static bool PLy_procedure_is_trigger(Form_pg_proc procStruct); +static PLyTrigType PLy_procedure_is_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); @@ -163,7 +165,7 @@ plpython3_validator(PG_FUNCTION_ARGS) Oid funcoid = PG_GETARG_OID(0); HeapTuple tuple; Form_pg_proc procStruct; - bool is_trigger; + PLyTrigType is_trigger; if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) PG_RETURN_VOID(); @@ -194,7 +196,7 @@ Datum plpython3_call_handler(PG_FUNCTION_ARGS) { bool nonatomic; - Datum retval; + Datum retval = (Datum) 0; PLyExecutionContext *exec_ctx; ErrorContextCallback plerrcontext; @@ -235,14 +237,21 @@ plpython3_call_handler(PG_FUNCTION_ARGS) Relation tgrel = ((TriggerData *) fcinfo->context)->tg_relation; HeapTuple trv; - proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), true); + proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), PLPY_TRIGGER); 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(funcoid, InvalidOid, PLPY_EVENT_TRIGGER); + exec_ctx->curr_proc = proc; + PLy_exec_event_trigger(fcinfo, proc); + /* there's no return value in this case */ + } else { - proc = PLy_procedure_get(funcoid, InvalidOid, false); + proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_NOT_TRIGGER); exec_ctx->curr_proc = proc; retval = PLy_exec_function(fcinfo, proc); } @@ -336,10 +345,25 @@ plpython3_inline_handler(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } -static bool +static PLyTrigType PLy_procedure_is_trigger(Form_pg_proc procStruct) { - return (procStruct->prorettype == TRIGGEROID); + PLyTrigType ret; + + switch (procStruct->prorettype) + { + case TRIGGEROID: + ret = PLPY_TRIGGER; + break; + case EVENT_TRIGGEROID: + ret = PLPY_EVENT_TRIGGER; + break; + default: + ret = PLPY_NOT_TRIGGER; + break; + } + + return ret; } static void diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c index c176d24e801..15b7c100144 100644 --- a/src/pl/plpython/plpy_procedure.c +++ b/src/pl/plpython/plpy_procedure.c @@ -21,7 +21,7 @@ static HTAB *PLy_procedure_cache = NULL; -static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger); +static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, PLyTrigType is_trigger); static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup); static char *PLy_procedure_munge_source(const char *name, const char *src); @@ -63,15 +63,18 @@ 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) +PLy_procedure_get(Oid fn_oid, Oid fn_rel, PLyTrigType is_trigger) { - bool use_cache = !(is_trigger && fn_rel == InvalidOid); + bool use_cache = true; HeapTuple procTup; PLyProcedureKey key; PLyProcedureEntry *volatile entry = NULL; PLyProcedure *volatile proc = NULL; bool found = false; + if (is_trigger == PLPY_TRIGGER && fn_rel == InvalidOid) + use_cache = false; + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid)); if (!HeapTupleIsValid(procTup)) elog(ERROR, "cache lookup failed for function %u", fn_oid); @@ -127,7 +130,7 @@ 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) +PLy_procedure_create(HeapTuple procTup, Oid fn_oid, PLyTrigType is_trigger) { char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; @@ -200,7 +203,7 @@ 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) + if (is_trigger == PLPY_NOT_TRIGGER) { Oid rettype = procStruct->prorettype; HeapTuple rvTypeTup; diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h index 5db854fc8bd..3ef22844a9b 100644 --- a/src/pl/plpython/plpy_procedure.h +++ b/src/pl/plpython/plpy_procedure.h @@ -11,6 +11,16 @@ extern void init_procedure_caches(void); +/* + * Trigger type + */ +typedef enum PLyTrigType +{ + PLPY_TRIGGER, + PLPY_EVENT_TRIGGER, + PLPY_NOT_TRIGGER, +} PLyTrigType; + /* saved arguments for outer recursion level or set-returning function */ typedef struct PLySavedArgs { @@ -33,7 +43,7 @@ typedef struct PLyProcedure bool fn_readonly; bool is_setof; /* true, if function returns result set */ bool is_procedure; - bool is_trigger; /* called as trigger? */ + PLyTrigType is_trigger; /* called as trigger? */ PLyObToDatum result; /* Function result output conversion info */ PLyDatumToOb result_in; /* For converting input tuples in a trigger */ char *src; /* textual procedure code, after mangling */ @@ -65,7 +75,7 @@ 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 PLyProcedure *PLy_procedure_get(Oid fn_oid, Oid fn_rel, PLyTrigType is_trigger); extern void PLy_procedure_compile(PLyProcedure *proc, const char *src); extern void PLy_procedure_delete(PLyProcedure *proc); diff --git a/src/pl/plpython/sql/plpython_trigger.sql b/src/pl/plpython/sql/plpython_trigger.sql index 440549c0785..92a712d35e8 100644 --- a/src/pl/plpython/sql/plpython_trigger.sql +++ b/src/pl/plpython/sql/plpython_trigger.sql @@ -492,3 +492,24 @@ CREATE TRIGGER recursive_trigger_trig INSERT INTO recursive_trigger_test VALUES (0, 0); UPDATE recursive_trigger_test SET a = 11 WHERE b = 0; SELECT * FROM recursive_trigger_test; + +-- event triggers + +CREATE OR REPLACE FUNCTION pysnitch() RETURNS event_trigger AS $$ + plpy.notice("TD[event] => " + str(TD["event"]) + " ; TD[tag] => " + str(TD["tag"])); +$$ LANGUAGE plpython3u; + +CREATE EVENT TRIGGER python_a_snitch ON ddl_command_start + EXECUTE PROCEDURE pysnitch(); +CREATE EVENT TRIGGER python_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 python_a_snitch; +DROP EVENT TRIGGER python_b_snitch; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e4a9ec65ab4..a13e8162890 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1990,6 +1990,7 @@ PLyScalarToOb PLySubtransactionData PLySubtransactionObject PLyTransformToOb +PLyTrigType PLyTupleToOb PLyUnicode_FromStringAndSize_t PLy_elog_impl_t