mirror of
https://github.com/postgres/postgres.git
synced 2025-10-16 17:07:43 +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>
|
<secondary>in PL/Python</secondary>
|
||||||
</indexterm>
|
</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>
|
<para>
|
||||||
When a function is used as a trigger, the dictionary
|
When a function is used as a trigger, the dictionary
|
||||||
<literal>TD</literal> contains trigger-related values:
|
<literal>TD</literal> contains trigger-related values:
|
||||||
@@ -769,6 +777,74 @@ $$ LANGUAGE plpython3u;
|
|||||||
</para>
|
</para>
|
||||||
</sect1>
|
</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">
|
<sect1 id="plpython-database">
|
||||||
<title>Database Access</title>
|
<title>Database Access</title>
|
||||||
|
|
||||||
|
@@ -646,3 +646,30 @@ SELECT * FROM recursive_trigger_test;
|
|||||||
1 | 2
|
1 | 2
|
||||||
(2 rows)
|
(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/htup_details.h"
|
||||||
#include "access/xact.h"
|
#include "access/xact.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 "funcapi.h"
|
#include "funcapi.h"
|
||||||
@@ -427,6 +428,47 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
|||||||
return rv;
|
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 */
|
/* helper functions for Python code execution */
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@@ -9,5 +9,6 @@
|
|||||||
|
|
||||||
extern Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc);
|
extern Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc);
|
||||||
extern HeapTuple PLy_exec_trigger(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 */
|
#endif /* PLPY_EXEC_H */
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#include "access/htup_details.h"
|
#include "access/htup_details.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 "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
@@ -240,6 +241,13 @@ plpython3_call_handler(PG_FUNCTION_ARGS)
|
|||||||
trv = PLy_exec_trigger(fcinfo, proc);
|
trv = PLy_exec_trigger(fcinfo, proc);
|
||||||
retval = PointerGetDatum(trv);
|
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
|
else
|
||||||
{
|
{
|
||||||
proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_NOT_TRIGGER);
|
proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_NOT_TRIGGER);
|
||||||
@@ -346,6 +354,9 @@ PLy_procedure_is_trigger(Form_pg_proc procStruct)
|
|||||||
case TRIGGEROID:
|
case TRIGGEROID:
|
||||||
ret = PLPY_TRIGGER;
|
ret = PLPY_TRIGGER;
|
||||||
break;
|
break;
|
||||||
|
case EVENT_TRIGGEROID:
|
||||||
|
ret = PLPY_EVENT_TRIGGER;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ret = PLPY_NOT_TRIGGER;
|
ret = PLPY_NOT_TRIGGER;
|
||||||
break;
|
break;
|
||||||
|
@@ -17,6 +17,7 @@ extern void init_procedure_caches(void);
|
|||||||
typedef enum PLyTrigType
|
typedef enum PLyTrigType
|
||||||
{
|
{
|
||||||
PLPY_TRIGGER,
|
PLPY_TRIGGER,
|
||||||
|
PLPY_EVENT_TRIGGER,
|
||||||
PLPY_NOT_TRIGGER,
|
PLPY_NOT_TRIGGER,
|
||||||
} PLyTrigType;
|
} PLyTrigType;
|
||||||
|
|
||||||
|
@@ -492,3 +492,27 @@ CREATE TRIGGER recursive_trigger_trig
|
|||||||
INSERT INTO recursive_trigger_test VALUES (0, 0);
|
INSERT INTO recursive_trigger_test VALUES (0, 0);
|
||||||
UPDATE recursive_trigger_test SET a = 11 WHERE b = 0;
|
UPDATE recursive_trigger_test SET a = 11 WHERE b = 0;
|
||||||
SELECT * FROM recursive_trigger_test;
|
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