1
0
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:
Peter Eisentraut
2025-08-21 09:15:55 +02:00
parent 6e09c960eb
commit 53eff471c6
7 changed files with 182 additions and 0 deletions

View File

@@ -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>

View File

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

View File

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

View File

@@ -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 */

View File

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

View File

@@ -17,6 +17,7 @@ extern void init_procedure_caches(void);
typedef enum PLyTrigType
{
PLPY_TRIGGER,
PLPY_EVENT_TRIGGER,
PLPY_NOT_TRIGGER,
} PLyTrigType;

View File

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