From: Tom Lane <tgl@sss.pgh.pa.us>
To: pgsql-hackers@lists.postgresql.org
Subject: Fix for recursive plpython triggers
Date: Sat, 04 May 2024 16:16:39 -0400
Message-ID: <3008982.1714853799@sss.pgh.pa.us> (raw)
This fixes bug #18456 [1]. Since we're in back-branch release freeze,
I'll just park it for the moment. But I think we should shove it in
once the freeze lifts so it's in 17beta1.
regards, tom lane
[1] https://www.postgresql.org/message-id/18456-82d3d70134aefd28%40postgresql.orgAttachments:v1-save-TD-in-recursive-plpython-triggers.patch (text/x-diff, 6.5K) downloaddiff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.outindex dd1ca32fa4..4cb90cb520 100644--- a/src/pl/plpython/expected/plpython_trigger.out+++ b/src/pl/plpython/expected/plpython_trigger.out@@ -618,3 +618,30 @@ SELECT * FROM trigger_test_generated;
---+---
(0 rows)
+-- recursive call of a trigger mustn't corrupt TD (bug #18456)+CREATE TABLE recursive_trigger_test (a int, b int);+CREATE FUNCTION recursive_trigger_func() RETURNS trigger+LANGUAGE plpython3u+AS $$+if TD["event"] == "UPDATE":+ plpy.execute("INSERT INTO recursive_trigger_test VALUES (1, 2)")+ plpy.notice("TD[event] => " + str(TD["event"]) + ", expecting UPDATE");+else:+ plpy.notice("TD[event] => " + str(TD["event"]) + ", expecting INSERT");+return None+$$;+CREATE TRIGGER recursive_trigger_trig+ AFTER INSERT OR UPDATE ON recursive_trigger_test+ FOR EACH ROW EXECUTE PROCEDURE recursive_trigger_func();+INSERT INTO recursive_trigger_test VALUES (0, 0);+NOTICE: TD[event] => INSERT, expecting INSERT+UPDATE recursive_trigger_test SET a = 11 WHERE b = 0;+NOTICE: TD[event] => INSERT, expecting INSERT+NOTICE: TD[event] => UPDATE, expecting UPDATE+SELECT * FROM recursive_trigger_test;+ a | b +----+---+ 11 | 0+ 1 | 2+(2 rows)+diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.cindex 3145c69699..6f7b5e121d 100644--- a/src/pl/plpython/plpy_exec.c+++ b/src/pl/plpython/plpy_exec.c@@ -335,6 +335,13 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
PLy_output_setup_tuple(&proc->result, rel_descr, proc);
PLy_input_setup_tuple(&proc->result_in, rel_descr, proc);
+ /*+ * If the trigger is called recursively, we must push outer-level+ * arguments into the stack. This must be immediately before the PG_TRY+ * to ensure that the corresponding pop happens.+ */+ PLy_global_args_push(proc);+
PG_TRY();
{
int rc PG_USED_FOR_ASSERTS_ONLY;
@@ -397,6 +404,7 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
}
PG_FINALLY();
{
+ PLy_global_args_pop(proc);
Py_XDECREF(plargs);
Py_XDECREF(plrv);
}
@@ -501,6 +509,13 @@ PLy_function_save_args(PLyProcedure *proc)
result->args = PyDict_GetItemString(proc->globals, "args");
Py_XINCREF(result->args);
+ /* If it's a trigger, also save "TD" */+ if (proc->is_trigger)+ {+ result->td = PyDict_GetItemString(proc->globals, "TD");+ Py_XINCREF(result->td);+ }+
/* Fetch all the named arguments */
if (proc->argnames)
{
@@ -550,6 +565,13 @@ PLy_function_restore_args(PLyProcedure *proc, PLySavedArgs *savedargs)
Py_DECREF(savedargs->args);
}
+ /* Restore the "TD" object, too */+ if (savedargs->td)+ {+ PyDict_SetItemString(proc->globals, "TD", savedargs->td);+ Py_DECREF(savedargs->td);+ }+
/* And free the PLySavedArgs struct */
pfree(savedargs);
}
@@ -568,8 +590,9 @@ PLy_function_drop_args(PLySavedArgs *savedargs)
Py_XDECREF(savedargs->namedargs[i]);
}
- /* Drop ref to the "args" object, too */+ /* Drop refs to the "args" and "TD" objects, too */
Py_XDECREF(savedargs->args);
+ Py_XDECREF(savedargs->td);
/* And free the PLySavedArgs struct */
pfree(savedargs);
@@ -578,9 +601,9 @@ PLy_function_drop_args(PLySavedArgs *savedargs)
/*
* Save away any existing arguments for the given procedure, so that we can
* install new values for a recursive call. This should be invoked before
- * doing PLy_function_build_args().+ * doing PLy_function_build_args() or PLy_trigger_build_args().
*
- * NB: caller must ensure that PLy_global_args_pop gets invoked once, and+ * NB: callers must ensure that PLy_global_args_pop gets invoked once, and
* only once, per successful completion of PLy_global_args_push. Otherwise
* we'll end up out-of-sync between the actual call stack and the contents
* of proc->argstack.
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.cindex 9cbbe7e3c8..ba7786d31c 100644--- a/src/pl/plpython/plpy_procedure.c+++ b/src/pl/plpython/plpy_procedure.c@@ -183,6 +183,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
proc->is_setof = procStruct->proretset;
proc->is_procedure = (procStruct->prokind == PROKIND_PROCEDURE);
+ proc->is_trigger = is_trigger;
proc->src = NULL;
proc->argnames = NULL;
proc->args = NULL;
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.hindex 8968b5c92e..5db854fc8b 100644--- a/src/pl/plpython/plpy_procedure.h+++ b/src/pl/plpython/plpy_procedure.h@@ -16,6 +16,7 @@ typedef struct PLySavedArgs
{
struct PLySavedArgs *next; /* linked-list pointer */
PyObject *args; /* "args" element of globals dict */
+ PyObject *td; /* "TD" element of globals dict, if trigger */
int nargs; /* length of namedargs array */
PyObject *namedargs[FLEXIBLE_ARRAY_MEMBER]; /* named args */
} PLySavedArgs;
@@ -32,6 +33,7 @@ typedef struct PLyProcedure
bool fn_readonly;
bool is_setof; /* true, if function returns result set */
bool is_procedure;
+ bool is_trigger; /* called as trigger? */
PLyObToDatum result; /* Function result output conversion info */
PLyDatumToOb result_in; /* For converting input tuples in a trigger */
char *src; /* textual procedure code, after mangling */
diff --git a/src/pl/plpython/sql/plpython_trigger.sql b/src/pl/plpython/sql/plpython_trigger.sqlindex e5504b9ab1..f6c2ef8d6a 100644--- a/src/pl/plpython/sql/plpython_trigger.sql+++ b/src/pl/plpython/sql/plpython_trigger.sql@@ -467,3 +467,27 @@ FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
TRUNCATE trigger_test_generated;
INSERT INTO trigger_test_generated (i) VALUES (1);
SELECT * FROM trigger_test_generated;
+++-- recursive call of a trigger mustn't corrupt TD (bug #18456)++CREATE TABLE recursive_trigger_test (a int, b int);++CREATE FUNCTION recursive_trigger_func() RETURNS trigger+LANGUAGE plpython3u+AS $$+if TD["event"] == "UPDATE":+ plpy.execute("INSERT INTO recursive_trigger_test VALUES (1, 2)")+ plpy.notice("TD[event] => " + str(TD["event"]) + ", expecting UPDATE");+else:+ plpy.notice("TD[event] => " + str(TD["event"]) + ", expecting INSERT");+return None+$$;++CREATE TRIGGER recursive_trigger_trig+ AFTER INSERT OR UPDATE ON recursive_trigger_test+ FOR EACH ROW EXECUTE PROCEDURE recursive_trigger_func();++INSERT INTO recursive_trigger_test VALUES (0, 0);+UPDATE recursive_trigger_test SET a = 11 WHERE b = 0;+SELECT * FROM recursive_trigger_test;
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: tgl@sss.pgh.pa.us, pgsql-hackers@lists.postgresql.org
Subject: Re: Fix for recursive plpython triggers
In-Reply-To: <3008982.1714853799@sss.pgh.pa.us>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox