Skip to content

Commit 53eff47

Browse files
petereDimitri Fontaine
andcommitted
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
1 parent 6e09c96 commit 53eff47

File tree

7 files changed

+182
-0
lines changed

7 files changed

+182
-0
lines changed

doc/src/sgml/plpython.sgml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,14 @@ $$ LANGUAGE plpython3u;
662662
<secondary>in PL/Python</secondary>
663663
</indexterm>
664664

665+
<para>
666+
<application>PL/Python</application> can be used to define trigger
667+
functions.
668+
<productname>PostgreSQL</productname> requires that a function that is to
669+
be called as a trigger must be declared as a function with no arguments and
670+
a return type of <literal>trigger</literal>.
671+
</para>
672+
665673
<para>
666674
When a function is used as a trigger, the dictionary
667675
<literal>TD</literal> contains trigger-related values:
@@ -769,6 +777,74 @@ $$ LANGUAGE plpython3u;
769777
</para>
770778
</sect1>
771779

780+
<sect1 id="plpython-event-trigger">
781+
<title>Event Trigger Functions</title>
782+
783+
<indexterm zone="plpython-event-trigger">
784+
<primary>event trigger</primary>
785+
<secondary>in PL/Python</secondary>
786+
</indexterm>
787+
788+
<para>
789+
<application>PL/Python</application> can be used to define event triggers
790+
(see also <xref linkend="event-triggers"/>).
791+
<productname>PostgreSQL</productname> requires that a function that is to
792+
be called as an event trigger must be declared as a function with no
793+
arguments and a return type of <literal>event_trigger</literal>.
794+
</para>
795+
796+
<para>
797+
When a function is used as an event trigger, the dictionary
798+
<literal>TD</literal> contains trigger-related values:
799+
800+
<variablelist>
801+
<varlistentry>
802+
<term><varname>TD["event"]</varname></term>
803+
<listitem>
804+
<para>
805+
The event the trigger was fired for, as a string, for example
806+
<literal>ddl_command_start</literal>.
807+
</para>
808+
</listitem>
809+
</varlistentry>
810+
811+
<varlistentry>
812+
<term><varname>TD["tag"]</varname></term>
813+
<listitem>
814+
<para>
815+
The command tag for which the trigger was fired, as a string, for
816+
example <literal>DROP TABLE</literal>.
817+
</para>
818+
</listitem>
819+
</varlistentry>
820+
</variablelist>
821+
</para>
822+
823+
<para>
824+
<xref linkend="plpython-event-trigger-example"/> shows an example of an
825+
event trigger function in <application>PL/Python</application>.
826+
</para>
827+
828+
<example id="plpython-event-trigger-example">
829+
<title>A <application>PL/Python</application> Event Trigger Function</title>
830+
831+
<para>
832+
This example trigger simply raises a <literal>NOTICE</literal> message
833+
each time a supported command is executed.
834+
</para>
835+
836+
<programlisting>
837+
CREATE OR REPLACE FUNCTION pysnitch() RETURNS event_trigger
838+
LANGUAGE plpython3u
839+
AS $$
840+
plpy.notice("TD[event] => " + TD["event"] + " ; TD[tag] => " + TD["tag"]);
841+
$$;
842+
843+
CREATE EVENT TRIGGER pysnitch ON ddl_command_start EXECUTE FUNCTION pysnitch();
844+
</programlisting>
845+
</example>
846+
</sect1>
847+
772848
<sect1 id="plpython-database">
773849
<title>Database Access</title>
774850

src/pl/plpython/expected/plpython_trigger.out

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,3 +646,30 @@ SELECT * FROM recursive_trigger_test;
646646
1 | 2
647647
(2 rows)
648648

649+
-- event triggers
650+
CREATE OR REPLACE FUNCTION pysnitch() RETURNS event_trigger
651+
LANGUAGE plpython3u
652+
AS $$
653+
plpy.notice("TD[event] => " + TD["event"] + " ; TD[tag] => " + TD["tag"]);
654+
$$;
655+
CREATE EVENT TRIGGER python_a_snitch ON ddl_command_start
656+
EXECUTE PROCEDURE pysnitch();
657+
CREATE EVENT TRIGGER python_b_snitch ON ddl_command_end
658+
EXECUTE PROCEDURE pysnitch();
659+
CREATE OR REPLACE FUNCTION foobar() RETURNS int LANGUAGE sql AS $$SELECT 1;$$;
660+
NOTICE: TD[event] => ddl_command_start ; TD[tag] => CREATE FUNCTION
661+
NOTICE: TD[event] => ddl_command_end ; TD[tag] => CREATE FUNCTION
662+
ALTER FUNCTION foobar() COST 77;
663+
NOTICE: TD[event] => ddl_command_start ; TD[tag] => ALTER FUNCTION
664+
NOTICE: TD[event] => ddl_command_end ; TD[tag] => ALTER FUNCTION
665+
DROP FUNCTION foobar();
666+
NOTICE: TD[event] => ddl_command_start ; TD[tag] => DROP FUNCTION
667+
NOTICE: TD[event] => ddl_command_end ; TD[tag] => DROP FUNCTION
668+
CREATE TABLE foo();
669+
NOTICE: TD[event] => ddl_command_start ; TD[tag] => CREATE TABLE
670+
NOTICE: TD[event] => ddl_command_end ; TD[tag] => CREATE TABLE
671+
DROP TABLE foo;
672+
NOTICE: TD[event] => ddl_command_start ; TD[tag] => DROP TABLE
673+
NOTICE: TD[event] => ddl_command_end ; TD[tag] => DROP TABLE
674+
DROP EVENT TRIGGER python_a_snitch;
675+
DROP EVENT TRIGGER python_b_snitch;

src/pl/plpython/plpy_exec.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "access/htup_details.h"
1010
#include "access/xact.h"
1111
#include "catalog/pg_type.h"
12+
#include "commands/event_trigger.h"
1213
#include "commands/trigger.h"
1314
#include "executor/spi.h"
1415
#include "funcapi.h"
@@ -427,6 +428,47 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
427428
return rv;
428429
}
429430

431+
/*
432+
* event trigger subhandler
433+
*/
434+
void
435+
PLy_exec_event_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
436+
{
437+
EventTriggerData *tdata;
438+
PyObject *volatile pltdata = NULL;
439+
440+
Assert(CALLED_AS_EVENT_TRIGGER(fcinfo));
441+
tdata = (EventTriggerData *) fcinfo->context;
442+
443+
PG_TRY();
444+
{
445+
PyObject *pltevent,
446+
*plttag;
447+
448+
pltdata = PyDict_New();
449+
if (!pltdata)
450+
PLy_elog(ERROR, NULL);
451+
452+
pltevent = PLyUnicode_FromString(tdata->event);
453+
PyDict_SetItemString(pltdata, "event", pltevent);
454+
Py_DECREF(pltevent);
455+
456+
plttag = PLyUnicode_FromString(GetCommandTagName(tdata->tag));
457+
PyDict_SetItemString(pltdata, "tag", plttag);
458+
Py_DECREF(plttag);
459+
460+
PLy_procedure_call(proc, "TD", pltdata);
461+
462+
if (SPI_finish() != SPI_OK_FINISH)
463+
elog(ERROR, "SPI_finish() failed");
464+
}
465+
PG_FINALLY();
466+
{
467+
Py_XDECREF(pltdata);
468+
}
469+
PG_END_TRY();
470+
}
471+
430472
/* helper functions for Python code execution */
431473

432474
static PyObject *

src/pl/plpython/plpy_exec.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99

1010
extern Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc);
1111
extern HeapTuple PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc);
12+
extern void PLy_exec_event_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc);
1213

1314
#endif /* PLPY_EXEC_H */

src/pl/plpython/plpy_main.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "access/htup_details.h"
1010
#include "catalog/pg_proc.h"
1111
#include "catalog/pg_type.h"
12+
#include "commands/event_trigger.h"
1213
#include "commands/trigger.h"
1314
#include "executor/spi.h"
1415
#include "miscadmin.h"
@@ -240,6 +241,13 @@ plpython3_call_handler(PG_FUNCTION_ARGS)
240241
trv = PLy_exec_trigger(fcinfo, proc);
241242
retval = PointerGetDatum(trv);
242243
}
244+
else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
245+
{
246+
proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_EVENT_TRIGGER);
247+
exec_ctx->curr_proc = proc;
248+
PLy_exec_event_trigger(fcinfo, proc);
249+
retval = (Datum) 0;
250+
}
243251
else
244252
{
245253
proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_NOT_TRIGGER);
@@ -346,6 +354,9 @@ PLy_procedure_is_trigger(Form_pg_proc procStruct)
346354
case TRIGGEROID:
347355
ret = PLPY_TRIGGER;
348356
break;
357+
case EVENT_TRIGGEROID:
358+
ret = PLPY_EVENT_TRIGGER;
359+
break;
349360
default:
350361
ret = PLPY_NOT_TRIGGER;
351362
break;

src/pl/plpython/plpy_procedure.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ extern void init_procedure_caches(void);
1717
typedef enum PLyTrigType
1818
{
1919
PLPY_TRIGGER,
20+
PLPY_EVENT_TRIGGER,
2021
PLPY_NOT_TRIGGER,
2122
} PLyTrigType;
2223

src/pl/plpython/sql/plpython_trigger.sql

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,3 +492,27 @@ CREATE TRIGGER recursive_trigger_trig
492492
INSERT INTO recursive_trigger_test VALUES (0, 0);
493493
UPDATE recursive_trigger_test SET a = 11 WHERE b = 0;
494494
SELECT * FROM recursive_trigger_test;
495+
496+
497+
-- event triggers
498+
499+
CREATE OR REPLACE FUNCTION pysnitch() RETURNS event_trigger
500+
LANGUAGE plpython3u
501+
AS $$
502+
plpy.notice("TD[event] => " + TD["event"] + " ; TD[tag] => " + TD["tag"]);
503+
$$;
504+
505+
CREATE EVENT TRIGGER python_a_snitch ON ddl_command_start
506+
EXECUTE PROCEDURE pysnitch();
507+
CREATE EVENT TRIGGER python_b_snitch ON ddl_command_end
508+
EXECUTE PROCEDURE pysnitch();
509+
510+
CREATE OR REPLACE FUNCTION foobar() RETURNS int LANGUAGE sql AS $$SELECT 1;$$;
511+
ALTER FUNCTION foobar() COST 77;
512+
DROP FUNCTION foobar();
513+
514+
CREATE TABLE foo();
515+
DROP TABLE foo;
516+
517+
DROP EVENT TRIGGER python_a_snitch;
518+
DROP EVENT TRIGGER python_b_snitch;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy