mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-03-07 19:47:50 +08:00
Fix trigger WHEN conditions when both BEFORE and AFTER triggers exist.
Due to tuple-slot mismanagement, evaluation of WHEN conditions for AFTER ROW UPDATE triggers could crash if there had been a BEFORE ROW trigger fired for the same update. Fix by not trying to overload the use of estate->es_trig_tuple_slot. Per report from Yoran Heling. Back-patch to 9.0, when trigger WHEN conditions were introduced.
This commit is contained in:
parent
6cc08e703b
commit
b33f78df17
@ -2770,13 +2770,13 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
|
||||
}
|
||||
if (HeapTupleIsValid(newtup))
|
||||
{
|
||||
if (estate->es_trig_tuple_slot == NULL)
|
||||
if (estate->es_trig_newtup_slot == NULL)
|
||||
{
|
||||
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
|
||||
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
|
||||
estate->es_trig_newtup_slot = ExecInitExtraTupleSlot(estate);
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
newslot = estate->es_trig_tuple_slot;
|
||||
newslot = estate->es_trig_newtup_slot;
|
||||
if (newslot->tts_tupleDescriptor != tupdesc)
|
||||
ExecSetSlotDescriptor(newslot, tupdesc);
|
||||
ExecStoreTuple(newtup, newslot, InvalidBuffer, false);
|
||||
|
@ -871,6 +871,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
|
||||
estate->es_tupleTable = NIL;
|
||||
estate->es_trig_tuple_slot = NULL;
|
||||
estate->es_trig_oldtup_slot = NULL;
|
||||
estate->es_trig_newtup_slot = NULL;
|
||||
|
||||
/* mark EvalPlanQual not active */
|
||||
estate->es_epqTuple = NULL;
|
||||
|
@ -124,6 +124,7 @@ CreateExecutorState(void)
|
||||
estate->es_trig_target_relations = NIL;
|
||||
estate->es_trig_tuple_slot = NULL;
|
||||
estate->es_trig_oldtup_slot = NULL;
|
||||
estate->es_trig_newtup_slot = NULL;
|
||||
|
||||
estate->es_param_list_info = NULL;
|
||||
estate->es_param_exec_vals = NULL;
|
||||
|
@ -354,7 +354,8 @@ typedef struct EState
|
||||
/* Stuff used for firing triggers: */
|
||||
List *es_trig_target_relations; /* trigger-only ResultRelInfos */
|
||||
TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
|
||||
TupleTableSlot *es_trig_oldtup_slot; /* for trigger old tuples */
|
||||
TupleTableSlot *es_trig_oldtup_slot; /* for TriggerEnabled */
|
||||
TupleTableSlot *es_trig_newtup_slot; /* for TriggerEnabled */
|
||||
|
||||
/* Parameter info: */
|
||||
ParamListInfo es_param_list_info; /* values of external params */
|
||||
|
@ -446,6 +446,35 @@ NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER,
|
||||
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
|
||||
NOTICE: trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
|
||||
NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
|
||||
--
|
||||
-- Test case for bug with BEFORE trigger followed by AFTER trigger with WHEN
|
||||
--
|
||||
CREATE TABLE some_t (some_col boolean NOT NULL);
|
||||
CREATE FUNCTION dummy_update_func() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'dummy_update_func(%) called: action = %, old = %, new = %',
|
||||
TG_ARGV[0], TG_OP, OLD, NEW;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER some_trig_before BEFORE UPDATE ON some_t FOR EACH ROW
|
||||
EXECUTE PROCEDURE dummy_update_func('before');
|
||||
CREATE TRIGGER some_trig_aftera AFTER UPDATE ON some_t FOR EACH ROW
|
||||
WHEN (NOT OLD.some_col AND NEW.some_col)
|
||||
EXECUTE PROCEDURE dummy_update_func('aftera');
|
||||
CREATE TRIGGER some_trig_afterb AFTER UPDATE ON some_t FOR EACH ROW
|
||||
WHEN (NOT NEW.some_col)
|
||||
EXECUTE PROCEDURE dummy_update_func('afterb');
|
||||
INSERT INTO some_t VALUES (TRUE);
|
||||
UPDATE some_t SET some_col = TRUE;
|
||||
NOTICE: dummy_update_func(before) called: action = UPDATE, old = (t), new = (t)
|
||||
UPDATE some_t SET some_col = FALSE;
|
||||
NOTICE: dummy_update_func(before) called: action = UPDATE, old = (t), new = (f)
|
||||
NOTICE: dummy_update_func(afterb) called: action = UPDATE, old = (t), new = (f)
|
||||
UPDATE some_t SET some_col = TRUE;
|
||||
NOTICE: dummy_update_func(before) called: action = UPDATE, old = (f), new = (t)
|
||||
NOTICE: dummy_update_func(aftera) called: action = UPDATE, old = (f), new = (t)
|
||||
DROP TABLE some_t;
|
||||
-- bogus cases
|
||||
CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col');
|
||||
|
@ -308,6 +308,32 @@ SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regc
|
||||
UPDATE main_table SET a = 50;
|
||||
UPDATE main_table SET b = 10;
|
||||
|
||||
--
|
||||
-- Test case for bug with BEFORE trigger followed by AFTER trigger with WHEN
|
||||
--
|
||||
|
||||
CREATE TABLE some_t (some_col boolean NOT NULL);
|
||||
CREATE FUNCTION dummy_update_func() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'dummy_update_func(%) called: action = %, old = %, new = %',
|
||||
TG_ARGV[0], TG_OP, OLD, NEW;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER some_trig_before BEFORE UPDATE ON some_t FOR EACH ROW
|
||||
EXECUTE PROCEDURE dummy_update_func('before');
|
||||
CREATE TRIGGER some_trig_aftera AFTER UPDATE ON some_t FOR EACH ROW
|
||||
WHEN (NOT OLD.some_col AND NEW.some_col)
|
||||
EXECUTE PROCEDURE dummy_update_func('aftera');
|
||||
CREATE TRIGGER some_trig_afterb AFTER UPDATE ON some_t FOR EACH ROW
|
||||
WHEN (NOT NEW.some_col)
|
||||
EXECUTE PROCEDURE dummy_update_func('afterb');
|
||||
INSERT INTO some_t VALUES (TRUE);
|
||||
UPDATE some_t SET some_col = TRUE;
|
||||
UPDATE some_t SET some_col = FALSE;
|
||||
UPDATE some_t SET some_col = TRUE;
|
||||
DROP TABLE some_t;
|
||||
|
||||
-- bogus cases
|
||||
CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col');
|
||||
|
Loading…
Reference in New Issue
Block a user