*** a/doc/src/sgml/pltcl.sgml --- b/doc/src/sgml/pltcl.sgml *************** *** 711,716 **** CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab --- 711,770 ---- + + Event Trigger Procedures in PL/Tcl + + + event trigger + in PL/Tcl + + + + Event trigger procedures can be written in PL/Tcl. + PostgreSQL requires that a procedure 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. + + + The information from the trigger manager is passed to the procedure body + in the following variables: + + + + + $TG_event + + + The name of the event the trigger is fired for. + + + + + + $TG_tag + + + The command tag for which the trigger is fired. + + + + + + + Here's a little example event trigger procedure that simply raises + a NOTICE message each time a supported command is + executed: + + + CREATE OR REPLACE FUNCTION tclsnitch() RETURNS event_trigger AS $$ + elog NOTICE "tclsnitch: $TG_event $TG_tag" + $$ LANGUAGE PLTCL; + + CREATE EVENT TRIGGER tcl_a_snitch ON ddl_command_start EXECUTE PROCEDURE tclsnitch(); + + + + Modules and the <function>unknown</> Command *** a/src/pl/tcl/expected/pltcl_setup.out --- b/src/pl/tcl/expected/pltcl_setup.out *************** *** 519,521 **** select tcl_date_week(2001,10,24); --- 519,544 ---- 42 (1 row) + -- test pltcl event triggers + create or replace function tclsnitch() returns event_trigger language pltcl as $$ + elog NOTICE " tclsnitch: $TG_event $TG_tag" + $$; + create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch(); + create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch(); + create or replace function foobar() returns int language sql as $$select 1;$$; + NOTICE: tclsnitch: ddl_command_start CREATE FUNCTION + NOTICE: tclsnitch: ddl_command_end CREATE FUNCTION + alter function foobar() cost 77; + NOTICE: tclsnitch: ddl_command_start ALTER FUNCTION + NOTICE: tclsnitch: ddl_command_end ALTER FUNCTION + drop function foobar(); + NOTICE: tclsnitch: ddl_command_start DROP FUNCTION + NOTICE: tclsnitch: ddl_command_end DROP FUNCTION + create table foo(); + NOTICE: tclsnitch: ddl_command_start CREATE TABLE + NOTICE: tclsnitch: ddl_command_end CREATE TABLE + drop table foo; + NOTICE: tclsnitch: ddl_command_start DROP TABLE + NOTICE: tclsnitch: ddl_command_end DROP TABLE + drop event trigger tcl_a_snitch; + drop event trigger tcl_b_snitch; *** a/src/pl/tcl/pltcl.c --- b/src/pl/tcl/pltcl.c *************** *** 27,32 **** --- 27,33 ---- #include "access/xact.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 "fmgr.h" *************** *** 200,210 **** static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted); static Datum pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted); static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted); static void throw_tcl_error(Tcl_Interp *interp, const char *proname); static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid, ! bool pltrusted); static int pltcl_elog(ClientData cdata, Tcl_Interp *interp, int argc, CONST84 char *argv[]); --- 201,213 ---- static Datum pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted); static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted); + static void pltcl_event_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted); static void throw_tcl_error(Tcl_Interp *interp, const char *proname); static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid, ! bool is_event_trigger, ! bool pltrusted); static int pltcl_elog(ClientData cdata, Tcl_Interp *interp, int argc, CONST84 char *argv[]); *************** *** 644,649 **** pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted) --- 647,658 ---- pltcl_current_fcinfo = NULL; retval = PointerGetDatum(pltcl_trigger_handler(fcinfo, pltrusted)); } + else if (CALLED_AS_EVENT_TRIGGER(fcinfo)) + { + pltcl_current_fcinfo = NULL; + pltcl_event_trigger_handler(fcinfo, pltrusted); + retval = (Datum) 0; + } else { pltcl_current_fcinfo = fcinfo; *************** *** 685,691 **** pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted) /* Find or compile the function */ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid, ! pltrusted); pltcl_current_prodesc = prodesc; --- 694,700 ---- /* Find or compile the function */ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid, ! false, pltrusted); pltcl_current_prodesc = prodesc; *************** *** 844,849 **** pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted) --- 853,859 ---- /* Find or compile the function */ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, RelationGetRelid(trigdata->tg_relation), + false, /* not a event trigger */ pltrusted); pltcl_current_prodesc = prodesc; *************** *** 1130,1135 **** pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted) --- 1140,1188 ---- return rettup; } + /********************************************************************** + * pltcl_event_trigger_handler() - Handler for event trigger calls + **********************************************************************/ + static void + pltcl_event_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted) + { + pltcl_proc_desc *prodesc; + Tcl_Interp *volatile interp; + EventTriggerData *tdata = (EventTriggerData *) fcinfo->context; + Tcl_DString tcl_cmd; + int tcl_rc; + + /* Connect to SPI manager */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "could not connect to SPI manager"); + + /* Find or compile the function */ + prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, + InvalidOid, true, pltrusted); + + pltcl_current_prodesc = prodesc; + + interp = prodesc->interp_desc->interp; + + /* Create the tcl command and call the internal proc */ + Tcl_DStringInit(&tcl_cmd); + Tcl_DStringAppendElement(&tcl_cmd, prodesc->internal_proname); + Tcl_DStringAppendElement(&tcl_cmd, tdata->event); + Tcl_DStringAppendElement(&tcl_cmd, tdata->tag); + + tcl_rc = Tcl_GlobalEval(interp, Tcl_DStringValue(&tcl_cmd)); + Tcl_DStringFree(&tcl_cmd); + + /* Check for errors reported by Tcl. */ + if (tcl_rc != TCL_OK) + throw_tcl_error(interp, prodesc->user_proname); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish() failed"); + + return; + } + /********************************************************************** * throw_tcl_error - ereport an error returned from the Tcl interpreter *************** *** 1168,1174 **** throw_tcl_error(Tcl_Interp *interp, const char *proname) * (InvalidOid) when compiling a plain function. **********************************************************************/ static pltcl_proc_desc * ! compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) { HeapTuple procTup; Form_pg_proc procStruct; --- 1221,1228 ---- * (InvalidOid) when compiling a plain function. **********************************************************************/ static pltcl_proc_desc * ! compile_pltcl_function(Oid fn_oid, Oid tgreloid, ! bool is_event_trigger, bool pltrusted) { HeapTuple procTup; Form_pg_proc procStruct; *************** *** 1245,1254 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) * "_trigger" when appropriate to ensure the normal and trigger * cases are kept separate. ************************************************************/ ! if (!is_trigger) snprintf(internal_proname, sizeof(internal_proname), "__PLTcl_proc_%u", fn_oid); ! else snprintf(internal_proname, sizeof(internal_proname), "__PLTcl_proc_%u_trigger", fn_oid); --- 1299,1311 ---- * "_trigger" when appropriate to ensure the normal and trigger * cases are kept separate. ************************************************************/ ! if (!is_trigger && !is_event_trigger) snprintf(internal_proname, sizeof(internal_proname), "__PLTcl_proc_%u", fn_oid); ! else if (is_event_trigger) ! snprintf(internal_proname, sizeof(internal_proname), ! "__PLTcl_proc_%u_evttrigger", fn_oid); ! else if (is_trigger) snprintf(internal_proname, sizeof(internal_proname), "__PLTcl_proc_%u_trigger", fn_oid); *************** *** 1286,1292 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) * Get the required information for input conversion of the * return value. ************************************************************/ ! if (!is_trigger) { typeTup = SearchSysCache1(TYPEOID, --- 1343,1349 ---- * Get the required information for input conversion of the * return value. ************************************************************/ ! if (!is_trigger && !is_event_trigger) { typeTup = SearchSysCache1(TYPEOID, *************** *** 1306,1312 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) { if (procStruct->prorettype == VOIDOID) /* okay */ ; ! else if (procStruct->prorettype == TRIGGEROID) { free(prodesc->user_proname); free(prodesc->internal_proname); --- 1363,1370 ---- { if (procStruct->prorettype == VOIDOID) /* okay */ ; ! else if (procStruct->prorettype == TRIGGEROID || ! procStruct->prorettype == EVTTRIGGEROID) { free(prodesc->user_proname); free(prodesc->internal_proname); *************** *** 1347,1353 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) * Get the required information for output conversion * of all procedure arguments ************************************************************/ ! if (!is_trigger) { prodesc->nargs = procStruct->pronargs; proc_internal_args[0] = '\0'; --- 1405,1411 ---- * Get the required information for output conversion * of all procedure arguments ************************************************************/ ! if (!is_trigger && !is_event_trigger) { prodesc->nargs = procStruct->pronargs; proc_internal_args[0] = '\0'; *************** *** 1397,1408 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) ReleaseSysCache(typeTup); } } ! else { /* trigger procedure has fixed args */ strcpy(proc_internal_args, "TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args"); } /************************************************************ * Create the tcl command to define the internal --- 1455,1471 ---- ReleaseSysCache(typeTup); } } ! else if (is_trigger) { /* trigger procedure has fixed args */ strcpy(proc_internal_args, "TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args"); } + else if (is_event_trigger) + { + /* event trigger procedure has fixed args */ + strcpy(proc_internal_args, "TG_event TG_tag"); + } /************************************************************ * Create the tcl command to define the internal *************** *** 1422,1441 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1); Tcl_DStringAppend(&proc_internal_body, internal_proname, -1); Tcl_DStringAppend(&proc_internal_body, " GD\n", -1); ! if (!is_trigger) ! { ! for (i = 0; i < prodesc->nargs; i++) ! { ! if (prodesc->arg_is_rowtype[i]) ! { ! snprintf(buf, sizeof(buf), ! "array set %d $__PLTcl_Tup_%d\n", ! i + 1, i + 1); ! Tcl_DStringAppend(&proc_internal_body, buf, -1); ! } ! } ! } ! else { Tcl_DStringAppend(&proc_internal_body, "array set NEW $__PLTcl_Tup_NEW\n", -1); --- 1485,1491 ---- Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1); Tcl_DStringAppend(&proc_internal_body, internal_proname, -1); Tcl_DStringAppend(&proc_internal_body, " GD\n", -1); ! if (is_trigger) { Tcl_DStringAppend(&proc_internal_body, "array set NEW $__PLTcl_Tup_NEW\n", -1); *************** *** 1451,1456 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) --- 1501,1523 ---- "}\n" "unset i v\n\n", -1); } + else if (is_event_trigger) + { + /* no argument support for event triggers */ + } + else + { + for (i = 0; i < prodesc->nargs; i++) + { + if (prodesc->arg_is_rowtype[i]) + { + snprintf(buf, sizeof(buf), + "array set %d $__PLTcl_Tup_%d\n", + i + 1, i + 1); + Tcl_DStringAppend(&proc_internal_body, buf, -1); + } + } + } /************************************************************ * Add user's function definition to proc body *** a/src/pl/tcl/sql/pltcl_setup.sql --- b/src/pl/tcl/sql/pltcl_setup.sql *************** *** 559,561 **** $$ language pltcl immutable; --- 559,579 ---- select tcl_date_week(2010,1,24); select tcl_date_week(2001,10,24); + + -- test pltcl event triggers + create or replace function tclsnitch() returns event_trigger language pltcl as $$ + elog NOTICE " tclsnitch: $TG_event $TG_tag" + $$; + + create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch(); + create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch(); + + 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 tcl_a_snitch; + drop event trigger tcl_b_snitch;