Fix PREPARE TRANSACTION to reject the case where the transaction has dropped a
authorTom Lane <[email protected]>
Tue, 4 Mar 2008 19:54:23 +0000 (19:54 +0000)
committerTom Lane <[email protected]>
Tue, 4 Mar 2008 19:54:23 +0000 (19:54 +0000)
temporary table; we can't support that because there's no way to clean up the
source backend's internal state if the eventual COMMIT PREPARED is done by
another backend.  This was checked correctly in 8.1 but I broke it in 8.2 :-(.
Patch by Heikki Linnakangas, original trouble report by John Smith.

src/backend/access/heap/heapam.c
src/backend/access/transam/xact.c
src/backend/storage/lmgr/lmgr.c
src/backend/storage/lmgr/lock.c
src/include/access/xact.h
src/include/storage/lmgr.h

index a7ad0848094e472ea8e7fac55c9b954c05d4bdba..41eb83d33e6fe1a340823c25d6559c61889d9e1a 100644 (file)
@@ -699,6 +699,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
        if (!RelationIsValid(r))
                elog(ERROR, "could not open relation with OID %u", relationId);
 
+       /* Make note that we've accessed a temporary relation */
+       if (r->rd_istemp)
+               MyXactAccessedTempRel = true;
+
        return r;
 }
 
@@ -741,6 +745,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
        if (!RelationIsValid(r))
                elog(ERROR, "could not open relation with OID %u", relationId);
 
+       /* Make note that we've accessed a temporary relation */
+       if (r->rd_istemp)
+               MyXactAccessedTempRel = true;
+
        return r;
 }
 
@@ -785,6 +793,10 @@ relation_open_nowait(Oid relationId, LOCKMODE lockmode)
        if (!RelationIsValid(r))
                elog(ERROR, "could not open relation with OID %u", relationId);
 
+       /* Make note that we've accessed a temporary relation */
+       if (r->rd_istemp)
+               MyXactAccessedTempRel = true;
+
        return r;
 }
 
index 384f5c9d6ba5fd0972bae3ec284e10bf47db2f62..4256af975b0a02b63d2bb0b05cfee736c467d14d 100644 (file)
@@ -57,6 +57,13 @@ bool         XactReadOnly;
 int                    CommitDelay = 0;        /* precommit delay in microseconds */
 int                    CommitSiblings = 5; /* # concurrent xacts needed to sleep */
 
+/*
+ * MyXactAccessedTempRel is set when a temporary relation is accessed.
+ * We don't allow PREPARE TRANSACTION in that case.  (This is global
+ * so that it can be set from heapam.c.)
+ */
+bool           MyXactAccessedTempRel = false;
+
 
 /*
  *     transaction states - transaction state from server perspective
@@ -1389,6 +1396,7 @@ StartTransaction(void)
        FreeXactSnapshot();
        XactIsoLevel = DefaultXactIsoLevel;
        XactReadOnly = DefaultXactReadOnly;
+       MyXactAccessedTempRel = false;
 
        /*
         * reinitialize within-transaction counters
@@ -1715,6 +1723,26 @@ PrepareTransaction(void)
 
        /* NOTIFY and flatfiles will be handled below */
 
+       /*
+        * Don't allow PREPARE TRANSACTION if we've accessed a temporary table
+        * in this transaction.  Having the prepared xact hold locks on another
+        * backend's temp table seems a bad idea --- for instance it would prevent
+        * the backend from exiting.  There are other problems too, such as how
+        * to clean up the source backend's local buffers and ON COMMIT state
+        * if the prepared xact includes a DROP of a temp table.
+        *
+        * We must check this after executing any ON COMMIT actions, because
+        * they might still access a temp relation.
+        *
+        * XXX In principle this could be relaxed to allow some useful special
+        * cases, such as a temp table created and dropped all within the
+        * transaction.  That seems to require much more bookkeeping though.
+        */
+       if (MyXactAccessedTempRel)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("cannot PREPARE a transaction that has operated on temporary tables")));
+
        /* Prevent cancel/die interrupt while cleaning up */
        HOLD_INTERRUPTS();
 
index e8cc29523f173617e1da99e9920bf702bd7ad20e..8b39bf4cae722659af576ab24c3dfd897ac038e3 100644 (file)
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
-#include "catalog/namespace.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/procarray.h"
 #include "utils/inval.h"
-#include "utils/lsyscache.h"
 
 
 /*
@@ -598,40 +596,3 @@ UnlockSharedObject(Oid classid, Oid objid, uint16 objsubid,
 
        LockRelease(&tag, lockmode, false);
 }
-
-
-/*
- * LockTagIsTemp
- *             Determine whether a locktag is for a lock on a temporary object
- *
- * We need this because 2PC cannot deal with temp objects
- */
-bool
-LockTagIsTemp(const LOCKTAG *tag)
-{
-       switch (tag->locktag_type)
-       {
-               case LOCKTAG_RELATION:
-               case LOCKTAG_RELATION_EXTEND:
-               case LOCKTAG_PAGE:
-               case LOCKTAG_TUPLE:
-                       /* check for lock on a temp relation */
-                       /* field1 is dboid, field2 is reloid for all of these */
-                       if ((Oid) tag->locktag_field1 == InvalidOid)
-                               return false;   /* shared, so not temp */
-                       if (isTempNamespace(get_rel_namespace((Oid) tag->locktag_field2)))
-                               return true;
-                       break;
-               case LOCKTAG_TRANSACTION:
-                       /* there are no temp transactions */
-                       break;
-               case LOCKTAG_OBJECT:
-                       /* there are currently no non-table temp objects */
-                       break;
-               case LOCKTAG_USERLOCK:
-               case LOCKTAG_ADVISORY:
-                       /* assume these aren't temp */
-                       break;
-       }
-       return false;                           /* default case */
-}
index 7cee97b1cfd9ca0e57f310e225d3f2741b0e62ac..1e3094e2b3912154682f41d71725752c438de6e3 100644 (file)
@@ -37,7 +37,6 @@
 #include "access/twophase_rmgr.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/lmgr.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "utils/resowner.h"
@@ -1849,12 +1848,6 @@ AtPrepare_Locks(void)
                                elog(ERROR, "cannot PREPARE when session locks exist");
                }
 
-               /* Can't handle it if the lock is on a temporary object */
-               if (LockTagIsTemp(&locallock->tag.lock))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("cannot PREPARE a transaction that has operated on temporary tables")));
-
                /*
                 * Create a 2PC record.
                 */
index 6543456aa72ddff5ffdd01730321c0c545a96f81..3ee7216b7039a81aa8e4bfa15b99d9da178e0a6d 100644 (file)
@@ -41,6 +41,9 @@ extern int    XactIsoLevel;
 extern bool DefaultXactReadOnly;
 extern bool XactReadOnly;
 
+/* Kluge for 2PC support */
+extern bool MyXactAccessedTempRel;
+
 /*
  *     start- and end-of-transaction callbacks for dynamically loaded modules
  */
index a6358c3be08aa2b07073eb4fb1d6c281430cd2e0..b104dfc2d84641150098f5c6b28ede589c1a86f2 100644 (file)
@@ -66,7 +66,4 @@ extern void LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
 extern void UnlockSharedObject(Oid classid, Oid objid, uint16 objsubid,
                                   LOCKMODE lockmode);
 
-/* Knowledge about which locktags describe temp objects */
-extern bool LockTagIsTemp(const LOCKTAG *tag);
-
 #endif   /* LMGR_H */