mirror of
https://github.com/postgres/postgres.git
synced 2025-05-19 00:04:06 -04:00
PL/Tcl: Add event trigger support
From: Dimitri Fontaine <dimitri@2ndQuadrant.fr>
This commit is contained in:
parent
45e02e3232
commit
a5036ca998
@ -711,6 +711,65 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
|
|||||||
</para>
|
</para>
|
||||||
</sect1>
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="pltcl-event-trigger">
|
||||||
|
<title>Event Trigger Procedures in PL/Tcl</title>
|
||||||
|
|
||||||
|
<indexterm>
|
||||||
|
<primary>event trigger</primary>
|
||||||
|
<secondary>in PL/Tcl</secondary>
|
||||||
|
</indexterm>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Event trigger procedures can be written in PL/Tcl.
|
||||||
|
<productname>PostgreSQL</productname> 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 <literal>event_trigger</>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The information from the trigger manager is passed to the procedure body
|
||||||
|
in the following variables:
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>$TG_event</varname></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The name of the event the trigger is fired for.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>$TG_tag</varname></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The command tag for which the trigger is fired.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The return value of the trigger procedure is ignored.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Here's a little example event trigger procedure that simply raises
|
||||||
|
a <literal>NOTICE</literal> message each time a supported command is
|
||||||
|
executed:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
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();
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
<sect1 id="pltcl-unknown">
|
<sect1 id="pltcl-unknown">
|
||||||
<title>Modules and the <function>unknown</> Command</title>
|
<title>Modules and the <function>unknown</> Command</title>
|
||||||
<para>
|
<para>
|
||||||
|
@ -519,3 +519,26 @@ select tcl_date_week(2001,10,24);
|
|||||||
42
|
42
|
||||||
(1 row)
|
(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;
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
|
#include "commands/event_trigger.h"
|
||||||
#include "commands/trigger.h"
|
#include "commands/trigger.h"
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
@ -200,11 +201,13 @@ static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted);
|
|||||||
static Datum pltcl_func_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 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 void throw_tcl_error(Tcl_Interp *interp, const char *proname);
|
||||||
|
|
||||||
static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||||
bool pltrusted);
|
bool is_event_trigger,
|
||||||
|
bool pltrusted);
|
||||||
|
|
||||||
static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
|
static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
|
||||||
int argc, CONST84 char *argv[]);
|
int argc, CONST84 char *argv[]);
|
||||||
@ -644,6 +647,12 @@ pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted)
|
|||||||
pltcl_current_fcinfo = NULL;
|
pltcl_current_fcinfo = NULL;
|
||||||
retval = PointerGetDatum(pltcl_trigger_handler(fcinfo, pltrusted));
|
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
|
else
|
||||||
{
|
{
|
||||||
pltcl_current_fcinfo = fcinfo;
|
pltcl_current_fcinfo = fcinfo;
|
||||||
@ -685,7 +694,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted)
|
|||||||
|
|
||||||
/* Find or compile the function */
|
/* Find or compile the function */
|
||||||
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid,
|
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid,
|
||||||
pltrusted);
|
false, pltrusted);
|
||||||
|
|
||||||
pltcl_current_prodesc = prodesc;
|
pltcl_current_prodesc = prodesc;
|
||||||
|
|
||||||
@ -844,6 +853,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
|
|||||||
/* Find or compile the function */
|
/* Find or compile the function */
|
||||||
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
|
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
|
||||||
RelationGetRelid(trigdata->tg_relation),
|
RelationGetRelid(trigdata->tg_relation),
|
||||||
|
false, /* not an event trigger */
|
||||||
pltrusted);
|
pltrusted);
|
||||||
|
|
||||||
pltcl_current_prodesc = prodesc;
|
pltcl_current_prodesc = prodesc;
|
||||||
@ -1130,6 +1140,47 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
|
|||||||
return rettup;
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* throw_tcl_error - ereport an error returned from the Tcl interpreter
|
* throw_tcl_error - ereport an error returned from the Tcl interpreter
|
||||||
@ -1168,7 +1219,8 @@ throw_tcl_error(Tcl_Interp *interp, const char *proname)
|
|||||||
* (InvalidOid) when compiling a plain function.
|
* (InvalidOid) when compiling a plain function.
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
static pltcl_proc_desc *
|
static pltcl_proc_desc *
|
||||||
compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
|
compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||||
|
bool is_event_trigger, bool pltrusted)
|
||||||
{
|
{
|
||||||
HeapTuple procTup;
|
HeapTuple procTup;
|
||||||
Form_pg_proc procStruct;
|
Form_pg_proc procStruct;
|
||||||
@ -1245,10 +1297,13 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
|
|||||||
* "_trigger" when appropriate to ensure the normal and trigger
|
* "_trigger" when appropriate to ensure the normal and trigger
|
||||||
* cases are kept separate.
|
* cases are kept separate.
|
||||||
************************************************************/
|
************************************************************/
|
||||||
if (!is_trigger)
|
if (!is_trigger && !is_event_trigger)
|
||||||
snprintf(internal_proname, sizeof(internal_proname),
|
snprintf(internal_proname, sizeof(internal_proname),
|
||||||
"__PLTcl_proc_%u", fn_oid);
|
"__PLTcl_proc_%u", fn_oid);
|
||||||
else
|
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),
|
snprintf(internal_proname, sizeof(internal_proname),
|
||||||
"__PLTcl_proc_%u_trigger", fn_oid);
|
"__PLTcl_proc_%u_trigger", fn_oid);
|
||||||
|
|
||||||
@ -1286,7 +1341,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
|
|||||||
* Get the required information for input conversion of the
|
* Get the required information for input conversion of the
|
||||||
* return value.
|
* return value.
|
||||||
************************************************************/
|
************************************************************/
|
||||||
if (!is_trigger)
|
if (!is_trigger && !is_event_trigger)
|
||||||
{
|
{
|
||||||
typeTup =
|
typeTup =
|
||||||
SearchSysCache1(TYPEOID,
|
SearchSysCache1(TYPEOID,
|
||||||
@ -1306,7 +1361,8 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
|
|||||||
{
|
{
|
||||||
if (procStruct->prorettype == VOIDOID)
|
if (procStruct->prorettype == VOIDOID)
|
||||||
/* okay */ ;
|
/* okay */ ;
|
||||||
else if (procStruct->prorettype == TRIGGEROID)
|
else if (procStruct->prorettype == TRIGGEROID ||
|
||||||
|
procStruct->prorettype == EVTTRIGGEROID)
|
||||||
{
|
{
|
||||||
free(prodesc->user_proname);
|
free(prodesc->user_proname);
|
||||||
free(prodesc->internal_proname);
|
free(prodesc->internal_proname);
|
||||||
@ -1347,7 +1403,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
|
|||||||
* Get the required information for output conversion
|
* Get the required information for output conversion
|
||||||
* of all procedure arguments
|
* of all procedure arguments
|
||||||
************************************************************/
|
************************************************************/
|
||||||
if (!is_trigger)
|
if (!is_trigger && !is_event_trigger)
|
||||||
{
|
{
|
||||||
prodesc->nargs = procStruct->pronargs;
|
prodesc->nargs = procStruct->pronargs;
|
||||||
proc_internal_args[0] = '\0';
|
proc_internal_args[0] = '\0';
|
||||||
@ -1397,12 +1453,17 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
|
|||||||
ReleaseSysCache(typeTup);
|
ReleaseSysCache(typeTup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (is_trigger)
|
||||||
{
|
{
|
||||||
/* trigger procedure has fixed args */
|
/* trigger procedure has fixed args */
|
||||||
strcpy(proc_internal_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");
|
"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
|
* Create the tcl command to define the internal
|
||||||
@ -1422,20 +1483,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
|
|||||||
Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1);
|
Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1);
|
||||||
Tcl_DStringAppend(&proc_internal_body, internal_proname, -1);
|
Tcl_DStringAppend(&proc_internal_body, internal_proname, -1);
|
||||||
Tcl_DStringAppend(&proc_internal_body, " GD\n", -1);
|
Tcl_DStringAppend(&proc_internal_body, " GD\n", -1);
|
||||||
if (!is_trigger)
|
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,
|
Tcl_DStringAppend(&proc_internal_body,
|
||||||
"array set NEW $__PLTcl_Tup_NEW\n", -1);
|
"array set NEW $__PLTcl_Tup_NEW\n", -1);
|
||||||
@ -1451,6 +1499,23 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
|
|||||||
"}\n"
|
"}\n"
|
||||||
"unset i v\n\n", -1);
|
"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
|
* Add user's function definition to proc body
|
||||||
|
@ -559,3 +559,21 @@ $$ language pltcl immutable;
|
|||||||
|
|
||||||
select tcl_date_week(2010,1,24);
|
select tcl_date_week(2010,1,24);
|
||||||
select tcl_date_week(2001,10,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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user