Fix for recursive plpython triggers - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Fix for recursive plpython triggers |
Date | |
Msg-id | 3008982.1714853799@sss.pgh.pa.us Whole thread Raw |
Responses |
Re: Fix for recursive plpython triggers
|
List | pgsql-hackers |
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.org diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out index 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.c index 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.c index 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.h index 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.sql index 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;
pgsql-hackers by date: