aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2011-08-21 18:16:02 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2011-08-21 18:16:02 -0400
commit38c9eb8fee298a9242bb2049c4fdf94407187d0c (patch)
treee403c803a48bf184cf83bb36db2d9c82fc469a7b
parent548d1f752be7a11e240f1eadd4f9987a7b9793c5 (diff)
downloadpostgresql-38c9eb8fee298a9242bb2049c4fdf94407187d0c.tar.gz
postgresql-38c9eb8fee298a9242bb2049c4fdf94407187d0c.zip
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.
-rw-r--r--src/backend/commands/trigger.c6
-rw-r--r--src/backend/executor/execMain.c1
-rw-r--r--src/backend/executor/execUtils.c1
-rw-r--r--src/include/nodes/execnodes.h8
-rw-r--r--src/test/regress/expected/triggers.out29
-rw-r--r--src/test/regress/sql/triggers.sql26
6 files changed, 67 insertions, 4 deletions
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 0b3f93faca7..f4352aef4eb 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2755,13 +2755,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);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 1d92f5980b4..4c05c80b272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -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;
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 073ef8d23b3..63e3d927726 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -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;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 16756616e52..187c70a0e01 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -354,7 +354,7 @@ 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 */
/* Parameter info: */
ParamListInfo es_param_list_info; /* values of external params */
@@ -401,6 +401,12 @@ typedef struct EState
HeapTuple *es_epqTuple; /* array of EPQ substitute tuples */
bool *es_epqTupleSet; /* true if EPQ tuple is provided */
bool *es_epqScanDone; /* true if EPQ tuple has been fetched */
+
+ /*
+ * this field added at end of struct to avoid post-release ABI breakage in
+ * existing release branches. It'll be in a more logical place in 9.2.
+ */
+ TupleTableSlot *es_trig_newtup_slot; /* for TriggerEnabled */
} EState;
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index c9e8c1a141b..b4d391974d2 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -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');
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 28928d5a936..e52131dbba2 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -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');