Skip to content

Commit b33f78d

Browse files
committed
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.
1 parent 6cc08e7 commit b33f78d

File tree

6 files changed

+62
-4
lines changed

6 files changed

+62
-4
lines changed

src/backend/commands/trigger.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2770,13 +2770,13 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
27702770
}
27712771
if (HeapTupleIsValid(newtup))
27722772
{
2773-
if (estate->es_trig_tuple_slot == NULL)
2773+
if (estate->es_trig_newtup_slot == NULL)
27742774
{
27752775
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
2776-
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
2776+
estate->es_trig_newtup_slot = ExecInitExtraTupleSlot(estate);
27772777
MemoryContextSwitchTo(oldContext);
27782778
}
2779-
newslot = estate->es_trig_tuple_slot;
2779+
newslot = estate->es_trig_newtup_slot;
27802780
if (newslot->tts_tupleDescriptor != tupdesc)
27812781
ExecSetSlotDescriptor(newslot, tupdesc);
27822782
ExecStoreTuple(newtup, newslot, InvalidBuffer, false);

src/backend/executor/execMain.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
871871
estate->es_tupleTable = NIL;
872872
estate->es_trig_tuple_slot = NULL;
873873
estate->es_trig_oldtup_slot = NULL;
874+
estate->es_trig_newtup_slot = NULL;
874875

875876
/* mark EvalPlanQual not active */
876877
estate->es_epqTuple = NULL;

src/backend/executor/execUtils.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ CreateExecutorState(void)
124124
estate->es_trig_target_relations = NIL;
125125
estate->es_trig_tuple_slot = NULL;
126126
estate->es_trig_oldtup_slot = NULL;
127+
estate->es_trig_newtup_slot = NULL;
127128

128129
estate->es_param_list_info = NULL;
129130
estate->es_param_exec_vals = NULL;

src/include/nodes/execnodes.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,8 @@ typedef struct EState
354354
/* Stuff used for firing triggers: */
355355
List *es_trig_target_relations; /* trigger-only ResultRelInfos */
356356
TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
357-
TupleTableSlot *es_trig_oldtup_slot; /* for trigger old tuples */
357+
TupleTableSlot *es_trig_oldtup_slot; /* for TriggerEnabled */
358+
TupleTableSlot *es_trig_newtup_slot; /* for TriggerEnabled */
358359

359360
/* Parameter info: */
360361
ParamListInfo es_param_list_info; /* values of external params */

src/test/regress/expected/triggers.out

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,35 @@ NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER,
446446
NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
447447
NOTICE: trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
448448
NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
449+
--
450+
-- Test case for bug with BEFORE trigger followed by AFTER trigger with WHEN
451+
--
452+
CREATE TABLE some_t (some_col boolean NOT NULL);
453+
CREATE FUNCTION dummy_update_func() RETURNS trigger AS $$
454+
BEGIN
455+
RAISE NOTICE 'dummy_update_func(%) called: action = %, old = %, new = %',
456+
TG_ARGV[0], TG_OP, OLD, NEW;
457+
RETURN NEW;
458+
END;
459+
$$ LANGUAGE plpgsql;
460+
CREATE TRIGGER some_trig_before BEFORE UPDATE ON some_t FOR EACH ROW
461+
EXECUTE PROCEDURE dummy_update_func('before');
462+
CREATE TRIGGER some_trig_aftera AFTER UPDATE ON some_t FOR EACH ROW
463+
WHEN (NOT OLD.some_col AND NEW.some_col)
464+
EXECUTE PROCEDURE dummy_update_func('aftera');
465+
CREATE TRIGGER some_trig_afterb AFTER UPDATE ON some_t FOR EACH ROW
466+
WHEN (NOT NEW.some_col)
467+
EXECUTE PROCEDURE dummy_update_func('afterb');
468+
INSERT INTO some_t VALUES (TRUE);
469+
UPDATE some_t SET some_col = TRUE;
470+
NOTICE: dummy_update_func(before) called: action = UPDATE, old = (t), new = (t)
471+
UPDATE some_t SET some_col = FALSE;
472+
NOTICE: dummy_update_func(before) called: action = UPDATE, old = (t), new = (f)
473+
NOTICE: dummy_update_func(afterb) called: action = UPDATE, old = (t), new = (f)
474+
UPDATE some_t SET some_col = TRUE;
475+
NOTICE: dummy_update_func(before) called: action = UPDATE, old = (f), new = (t)
476+
NOTICE: dummy_update_func(aftera) called: action = UPDATE, old = (f), new = (t)
477+
DROP TABLE some_t;
449478
-- bogus cases
450479
CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table
451480
FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col');

src/test/regress/sql/triggers.sql

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,32 @@ SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regc
308308
UPDATE main_table SET a = 50;
309309
UPDATE main_table SET b = 10;
310310

311+
--
312+
-- Test case for bug with BEFORE trigger followed by AFTER trigger with WHEN
313+
--
314+
315+
CREATE TABLE some_t (some_col boolean NOT NULL);
316+
CREATE FUNCTION dummy_update_func() RETURNS trigger AS $$
317+
BEGIN
318+
RAISE NOTICE 'dummy_update_func(%) called: action = %, old = %, new = %',
319+
TG_ARGV[0], TG_OP, OLD, NEW;
320+
RETURN NEW;
321+
END;
322+
$$ LANGUAGE plpgsql;
323+
CREATE TRIGGER some_trig_before BEFORE UPDATE ON some_t FOR EACH ROW
324+
EXECUTE PROCEDURE dummy_update_func('before');
325+
CREATE TRIGGER some_trig_aftera AFTER UPDATE ON some_t FOR EACH ROW
326+
WHEN (NOT OLD.some_col AND NEW.some_col)
327+
EXECUTE PROCEDURE dummy_update_func('aftera');
328+
CREATE TRIGGER some_trig_afterb AFTER UPDATE ON some_t FOR EACH ROW
329+
WHEN (NOT NEW.some_col)
330+
EXECUTE PROCEDURE dummy_update_func('afterb');
331+
INSERT INTO some_t VALUES (TRUE);
332+
UPDATE some_t SET some_col = TRUE;
333+
UPDATE some_t SET some_col = FALSE;
334+
UPDATE some_t SET some_col = TRUE;
335+
DROP TABLE some_t;
336+
311337
-- bogus cases
312338
CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table
313339
FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col');

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