]> granicus.if.org Git - postgresql/commitdiff
Fix trigger WHEN conditions when both BEFORE and AFTER triggers exist.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 21 Aug 2011 22:15:55 +0000 (18:15 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 21 Aug 2011 22:15:55 +0000 (18:15 -0400)
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.

src/backend/commands/trigger.c
src/backend/executor/execMain.c
src/backend/executor/execUtils.c
src/include/nodes/execnodes.h
src/test/regress/expected/triggers.out
src/test/regress/sql/triggers.sql

index 4c31f19af958cd1cf35eef236092a86da07692f4..680962aa44499b67478075ad62cc9c0f8e079e0b 100644 (file)
@@ -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);
index eacd863647d2b09de71670806d8cbe84a93159ce..1dfe8b9ac7823f8c92468178991107931a858ff5 100644 (file)
@@ -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;
index 073ef8d23b3688e3e1f2034c5badc6a51f183c01..63e3d9277264d251c5df6e6ac8c417d5b0a4dc49 100644 (file)
@@ -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;
index a3a931005585378001db56c76d4e50e5d406f5a5..6ed739c290cc4a862d120c811592a25d5f818a6b 100644 (file)
@@ -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 */
index c9e8c1a141b9eccd0f5f83a2ce12c3192efe959b..b4d391974d21e9e94e75124c1cd0e022527f68e9 100644 (file)
@@ -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');
index 28928d5a936072bfbbc24b5c60e6d8a707a2ee82..e52131dbba2d7a45c5f9365f24721de8a07e3253 100644 (file)
@@ -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');