From f8bbfad075e855d4603e44847cd6ec51c91b3f92 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
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 7e8884496d0..45167b816af 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 42d89a9a21a..23dc5030397 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 0cb4df7c4fa..31253d8b4fe 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);
 
 
 /*
-- 
GitLab