Disallow TRUNCATE when there are any pending after-trigger events for
authorTom Lane
Mon, 4 Sep 2006 21:15:56 +0000 (21:15 +0000)
committerTom Lane
Mon, 4 Sep 2006 21:15:56 +0000 (21:15 +0000)
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
src/backend/commands/trigger.c
src/include/commands/trigger.h

index 7e8884496d0bcc24412de7c21d6401e76cde9e3e..45167b816af42e259cf48cc595fa7bad29d614e5 100644 (file)
@@ -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.
     */
index 42d89a9a21a3c714f0ff8261065986b28b7c1e63..23dc5030397e1d2ef3c9ed92ce5ba2234f9431d8 100644 (file)
@@ -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()
index 0cb4df7c4fa2d29c01a82eaf59a63c83ddbf0d5c..31253d8b4fe1561ce8c0b3c3e94fc717e75a0386 100644 (file)
@@ -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);
 
 
 /*