From f8bbfad075e855d4603e44847cd6ec51c91b3f92 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 4 Sep 2006 21:15:56 +0000 Subject: [PATCH] Disallow TRUNCATE when there are any pending after-trigger events for the target relation(s). There might be some cases where we could discard the pending event instead, but for the moment a conservative approach seems sufficient. Per report from Markus Schiltknecht and subsequent discussion. --- src/backend/commands/tablecmds.c | 9 +++- src/backend/commands/trigger.c | 70 +++++++++++++++++++++++++++++++- src/include/commands/trigger.h | 4 +- 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 7e8884496d..45167b816a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.201 2006/08/25 04:06:48 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.202 2006/09/04 21:15:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -611,6 +611,13 @@ ExecuteTruncate(TruncateStmt *stmt) heap_truncate_check_FKs(rels, false); #endif + /* + * Also check for pending AFTER trigger events on the target relations. + * We can't just leave those be, since they will try to fetch tuples + * that the TRUNCATE removes. + */ + AfterTriggerCheckTruncate(relids); + /* * OK, truncate each table. */ diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 42d89a9a21..23dc503039 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.206 2006/08/03 16:04:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.207 2006/09/04 21:15:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -3117,6 +3117,74 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) } } +/* ---------- + * AfterTriggerCheckTruncate() + * Test deferred-trigger status to see if a TRUNCATE is OK. + * + * The argument is a list of OIDs of relations due to be truncated. + * We raise error if there are any pending after-trigger events for them. + * + * In some scenarios it'd be reasonable to remove pending events (more + * specifically, mark them DONE by the current subxact) but without a lot + * of knowledge of the trigger semantics we can't do this in general. + * ---------- + */ +void +AfterTriggerCheckTruncate(List *relids) +{ + AfterTriggerEvent event; + int depth; + + /* + * Ignore call if we aren't in a transaction. (Shouldn't happen?) + */ + if (afterTriggers == NULL) + return; + + /* Scan queued events */ + for (event = afterTriggers->events.head; + event != NULL; + event = event->ate_next) + { + /* + * We can ignore completed events. (Even if a DONE flag is rolled + * back by subxact abort, it's OK because the effects of the + * TRUNCATE must get rolled back too.) + */ + if (event->ate_event & AFTER_TRIGGER_DONE) + continue; + + if (list_member_oid(relids, event->ate_relid)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot truncate table \"%s\" because it has pending trigger events", + get_rel_name(event->ate_relid)))); + } + + /* + * Also scan events queued by incomplete queries. This could only + * matter if a TRUNCATE is executed by a function or trigger within + * an updating query on the same relation, which is pretty perverse, + * but let's check. + */ + for (depth = 0; depth <= afterTriggers->query_depth; depth++) + { + for (event = afterTriggers->query_stack[depth].head; + event != NULL; + event = event->ate_next) + { + if (event->ate_event & AFTER_TRIGGER_DONE) + continue; + + if (list_member_oid(relids, event->ate_relid)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot truncate table \"%s\" because it has pending trigger events", + get_rel_name(event->ate_relid)))); + } + } +} + /* ---------- * AfterTriggerSaveEvent() diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0cb4df7c4f..31253d8b4f 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.58 2006/06/16 20:23:45 adunstan Exp $ + * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.59 2006/09/04 21:15:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -164,8 +164,8 @@ extern void AfterTriggerFireDeferred(void); extern void AfterTriggerEndXact(bool isCommit); extern void AfterTriggerBeginSubXact(void); extern void AfterTriggerEndSubXact(bool isCommit); - extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); +extern void AfterTriggerCheckTruncate(List *relids); /*