mirror of
https://github.com/postgres/postgres.git
synced 2025-10-15 05:46:52 +03:00
PL/Python: Add event trigger support
Allow event triggers to be written in PL/Python. It provides a TD dictionary with some information about the event trigger. Author: Euler Taveira <euler@eulerto.com> Co-authored-by: Dimitri Fontaine <dimitri@2ndQuadrant.fr> Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/03f03515-2068-4f5b-b357-8fb540883c38%40app.fastmail.com
This commit is contained in:
@@ -662,6 +662,14 @@ $$ LANGUAGE plpython3u;
|
||||
<secondary>in PL/Python</secondary>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
<application>PL/Python</application> can be used to define trigger
|
||||
functions.
|
||||
<productname>PostgreSQL</productname> requires that a function that is to
|
||||
be called as a trigger must be declared as a function with no arguments and
|
||||
a return type of <literal>trigger</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When a function is used as a trigger, the dictionary
|
||||
<literal>TD</literal> contains trigger-related values:
|
||||
@@ -769,6 +777,74 @@ $$ LANGUAGE plpython3u;
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="plpython-event-trigger">
|
||||
<title>Event Trigger Functions</title>
|
||||
|
||||
<indexterm zone="plpython-event-trigger">
|
||||
<primary>event trigger</primary>
|
||||
<secondary>in PL/Python</secondary>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
<application>PL/Python</application> can be used to define event triggers
|
||||
(see also <xref linkend="event-triggers"/>).
|
||||
<productname>PostgreSQL</productname> 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 <literal>event_trigger</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When a function is used as an event trigger, the dictionary
|
||||
<literal>TD</literal> contains trigger-related values:
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>TD["event"]</varname></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The event the trigger was fired for, as a string, for example
|
||||
<literal>ddl_command_start</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>TD["tag"]</varname></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The command tag for which the trigger was fired, as a string, for
|
||||
example <literal>DROP TABLE</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<xref linkend="plpython-event-trigger-example"/> shows an example of an
|
||||
event trigger function in <application>PL/Python</application>.
|
||||
</para>
|
||||
|
||||
<example id="plpython-event-trigger-example">
|
||||
<title>A <application>PL/Python</application> Event Trigger Function</title>
|
||||
|
||||
<para>
|
||||
This example trigger simply raises a <literal>NOTICE</literal> message
|
||||
each time a supported command is executed.
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
CREATE OR REPLACE FUNCTION pysnitch() RETURNS event_trigger
|
||||
LANGUAGE plpython3u
|
||||
AS $$
|
||||
plpy.notice("TD[event] => " + TD["event"] + " ; TD[tag] => " + TD["tag"]);
|
||||
$$;
|
||||
|
||||
CREATE EVENT TRIGGER pysnitch ON ddl_command_start EXECUTE FUNCTION pysnitch();
|
||||
</programlisting>
|
||||
</example>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="plpython-database">
|
||||
<title>Database Access</title>
|
||||
|
||||
|
@@ -646,3 +646,30 @@ SELECT * FROM recursive_trigger_test;
|
||||
1 | 2
|
||||
(2 rows)
|
||||
|
||||
-- event triggers
|
||||
CREATE OR REPLACE FUNCTION pysnitch() RETURNS event_trigger
|
||||
LANGUAGE plpython3u
|
||||
AS $$
|
||||
plpy.notice("TD[event] => " + TD["event"] + " ; TD[tag] => " + TD["tag"]);
|
||||
$$;
|
||||
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;
|
||||
|
@@ -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,47 @@ 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 *
|
||||
|
@@ -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 */
|
||||
|
@@ -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"
|
||||
@@ -240,6 +241,13 @@ plpython3_call_handler(PG_FUNCTION_ARGS)
|
||||
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);
|
||||
retval = (Datum) 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_NOT_TRIGGER);
|
||||
@@ -346,6 +354,9 @@ PLy_procedure_is_trigger(Form_pg_proc procStruct)
|
||||
case TRIGGEROID:
|
||||
ret = PLPY_TRIGGER;
|
||||
break;
|
||||
case EVENT_TRIGGEROID:
|
||||
ret = PLPY_EVENT_TRIGGER;
|
||||
break;
|
||||
default:
|
||||
ret = PLPY_NOT_TRIGGER;
|
||||
break;
|
||||
|
@@ -17,6 +17,7 @@ extern void init_procedure_caches(void);
|
||||
typedef enum PLyTrigType
|
||||
{
|
||||
PLPY_TRIGGER,
|
||||
PLPY_EVENT_TRIGGER,
|
||||
PLPY_NOT_TRIGGER,
|
||||
} PLyTrigType;
|
||||
|
||||
|
@@ -492,3 +492,27 @@ 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
|
||||
LANGUAGE plpython3u
|
||||
AS $$
|
||||
plpy.notice("TD[event] => " + TD["event"] + " ; TD[tag] => " + TD["tag"]);
|
||||
$$;
|
||||
|
||||
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;
|
||||
|
Reference in New Issue
Block a user