Transactions and temp tables - Mailing list pgsql-hackers

From Emmanuel Cecchet
Subject Transactions and temp tables
Date
Msg-id 48EA7B12.6050906@frogthinker.org
Whole thread Raw
Responses Re: Transactions and temp tables
List pgsql-hackers
Hi,

I had the same problem as John with "could not open relation
1663/16384/16584: No such file or directory" in a specific combination
of transactions with temp tables
(http://archives.postgresql.org/pgsql-hackers/2008-02/msg01260.php). As
Heikki mentioned
(http://archives.postgresql.org/pgsql-hackers/2008-02/msg01277.php) we
should be able to allow CREATE+DROP in the same transaction.

I came up with a patch (currently based on 8.3.3) to address that issue.
Instead of relying on a boolean that tells if a temp table was accessed,
I keep a list of the Oid for the temp tables accessed in the transaction
and at prepare commit time, I check if the relations are still valid. I
also added a check to allow empty temp tables at prepare commit time
(this allows to use temp tables with 'on commit delete rows' options.

I am attaching the patch and the use cases I have been using to test it.
The test cases try to compile the various use cases that I have seen
reported on the list. Let me know what you think of the patch and if it
could be applied to 8.3 and 8.4?

Thanks in advance for your feedback,
manu

--
Emmanuel Cecchet
FTO @ Frog Thinker
Open Source Development & Consulting
--
Web: http://www.frogthinker.org
email: manu@frogthinker.org
Skype: emmanuel_cecchet

### Eclipse Workspace Patch 1.0
#P Postgres-8.3.3
Index: src/include/access/xact.h
===================================================================
RCS file: /root/cvsrepo/pgsql/src/include/access/xact.h,v
retrieving revision 1.93.2.1
diff -u -r1.93.2.1 xact.h
--- src/include/access/xact.h    4 Mar 2008 19:54:13 -0000    1.93.2.1
+++ src/include/access/xact.h    6 Oct 2008 20:39:46 -0000
@@ -18,6 +18,7 @@
 #include "nodes/pg_list.h"
 #include "storage/relfilenode.h"
 #include "utils/timestamp.h"
+#include "postgres_ext.h"


 /*
@@ -44,8 +45,8 @@
 /* Asynchronous commits */
 extern bool XactSyncCommit;

-/* Kluge for 2PC support */
-extern bool MyXactAccessedTempRel;
+/* List of temp tables accessed during a transaction for 2PC support */
+extern void enlistRelationIdFor2PCChecks(Oid relationId);

 /*
  *    start- and end-of-transaction callbacks for dynamically loaded modules
Index: src/backend/access/heap/heapam.c
===================================================================
RCS file: /root/cvsrepo/pgsql/src/backend/access/heap/heapam.c,v
retrieving revision 1.249.2.2
diff -u -r1.249.2.2 heapam.c
--- src/backend/access/heap/heapam.c    8 Mar 2008 21:58:07 -0000    1.249.2.2
+++ src/backend/access/heap/heapam.c    6 Oct 2008 20:39:46 -0000
@@ -870,7 +870,7 @@

     /* Make note that we've accessed a temporary relation */
     if (r->rd_istemp)
-        MyXactAccessedTempRel = true;
+        enlistRelationIdFor2PCChecks(relationId);

     pgstat_initstats(r);

@@ -918,7 +918,7 @@

     /* Make note that we've accessed a temporary relation */
     if (r->rd_istemp)
-        MyXactAccessedTempRel = true;
+        enlistRelationIdFor2PCChecks(relationId);

     pgstat_initstats(r);

@@ -968,7 +968,7 @@

     /* Make note that we've accessed a temporary relation */
     if (r->rd_istemp)
-        MyXactAccessedTempRel = true;
+        enlistRelationIdFor2PCChecks(relationId);

     pgstat_initstats(r);

Index: src/backend/access/transam/xact.c
===================================================================
RCS file: /root/cvsrepo/pgsql/src/backend/access/transam/xact.c,v
retrieving revision 1.257.2.2
diff -u -r1.257.2.2 xact.c
--- src/backend/access/transam/xact.c    26 Apr 2008 23:35:33 -0000    1.257.2.2
+++ src/backend/access/transam/xact.c    6 Oct 2008 20:39:46 -0000
@@ -62,13 +62,6 @@
 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
@@ -206,6 +199,10 @@
  */
 static MemoryContext TransactionAbortContext = NULL;

+#define    MAX_TEMP_TABLES_IN_A_TX    10
+#define    UNUSED_OID                0
+Oid        accessedTempRel[MAX_TEMP_TABLES_IN_A_TX]; /* Oids of accessed temporary relations */
+
 /*
  * List of add-on start- and end-of-xact callbacks
  */
@@ -1512,7 +1509,11 @@
     XactIsoLevel = DefaultXactIsoLevel;
     XactReadOnly = DefaultXactReadOnly;
     forceSyncCommit = false;
-    MyXactAccessedTempRel = false;
+    {
+        int i;
+        for (i = 0 ; i < MAX_TEMP_TABLES_IN_A_TX ; i++)
+            accessedTempRel[i] = UNUSED_OID;
+    }

     /*
      * reinitialize within-transaction counters
@@ -1777,6 +1778,38 @@
     RESUME_INTERRUPTS();
 }

+/* ----------------
+ *     enlistRelationIdFor2PCChecks - enlist a relation in the list of
+ *      resources to check at PREPARE COMMIT time if we are part of
+ *      a 2PC transaction. The resource will be removed from the list
+ *      if the table is dropped before commit.
+ * ----------------
+ */
+void
+enlistRelationIdFor2PCChecks(Oid relationId)
+{
+    /*
+     * Each time a temporary relation is accessed, it is added to the
+     * accessedTempRel list. PREPARE TRANSACTION will fail if any
+     * of the accessed relation is still valid (not dropped).  (This is
+     * called from from heapam.c.)
+     */
+    int  i;
+    bool fullOidList = true;
+
+    for (i = 0 ; i < MAX_TEMP_TABLES_IN_A_TX ; i++)
+    { /* Enlist the relation id in the first available slot */
+        if ((accessedTempRel[i] == UNUSED_OID) ||
+            (accessedTempRel[i] == relationId))
+        {
+            accessedTempRel[i] = relationId;
+            fullOidList = false;
+            break;
+        }
+    }
+    if (fullOidList)
+        elog(ERROR, "Too many temp tables accessed inside the same transaction. Increase MAX_TEMP_TABLES_IN_A_TX in
xact.c,list is full"); 
+}

 /*
  *    PrepareTransaction
@@ -1853,14 +1886,36 @@
      * 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.
+     * We only allow to proceed further if the accessed temp tables have
+     * been dropped before PREPARE COMMIT.
      */
-    if (MyXactAccessedTempRel)
-        ereport(ERROR,
-                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                 errmsg("cannot PREPARE a transaction that has operated on temporary tables")));
+    {
+        int  i;
+        for (i = 0; i < MAX_TEMP_TABLES_IN_A_TX; i++)
+        { /* Check all accessed temp tables */
+            if (accessedTempRel[i] != UNUSED_OID)
+            { /* If the table has been dropped, try_relation_open will fail and
+                 we can safely continue. */
+                Relation tempTable = try_relation_open(accessedTempRel[i], NoLock);
+                if (tempTable != NULL)
+                { /* We have an open temp table at PREPARE COMMIT time. We
+                     will only accept empty temp tables and throw an error
+                     in other cases. */
+                    HeapScanDesc scan;
+                    HeapTuple tuple;
+                    scan = heap_beginscan(tempTable, SnapshotNow, 0, NULL);
+                    if ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+                    {
+                        ereport(ERROR,
+                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("cannot PREPARE a transaction that has operated on temporary tables
thatare not empty at commit time"))); 
+                    }
+                    heap_endscan(scan);
+                    relation_close(tempTable, NoLock);
+                }
+            }
+        }
+    }

     /* Prevent cancel/die interrupt while cleaning up */
     HOLD_INTERRUPTS();
\echo Creation of a persistent table (not temp)
begin;
create table paul(x int);
insert into paul values(1);
prepare transaction 'persistentTableShouldSucceed';
commit prepared 'persistentTableShouldSucceed';

\echo Drop of a persistent table (not temp)
begin;
drop table paul;
prepare transaction 'dropPersistentTableShouldSucceed';
commit prepared 'dropPersistentTableShouldSucceed';

\echo Transaction-scope dropped temp table use case
begin;
create temp table foo(x int);
insert into foo values(1);
drop table foo;
prepare transaction 'dropTempTableShouldSucceed';
commit prepared 'dropTempTableShouldSucceed';

\echo Session-scope temp table use case
create temp table foo(x int);
begin;
insert into foo values(1);
delete from foo;
prepare transaction 'dropTempTableShouldSucceed';
commit prepared 'dropTempTableShouldSucceed';
drop table foo;

\echo Temp table with ON COMMIT DROP option
begin;
create temp table foo(x int) on commit drop;
insert into foo values(1);
prepare transaction 'onCommitDropTempTableShouldSucceed';
commit prepared 'onCommitDropTempTableShouldSucceed';

\echo Temp table with ON DELETE ROWS option (transaction scope)
begin;
create temp table foo(x int) on commit delete rows;
insert into foo values(1);
prepare transaction 'onCommitDeleteRowsTempTableShouldSucceed';
commit prepared 'onCommitDeleteRowsTempTableShouldSucceed';
drop table foo;

\echo Temp table with ON DELETE ROWS option (session scope)
create temp table foo(x int) on commit delete rows;
begin;
insert into foo values(1);
prepare transaction 'onCommitDeleteRowsTempTableShouldSucceed';
commit prepared 'onCommitDeleteRowsTempTableShouldSucceed';
drop table foo;

\echo Rollback to savepoint test case
BEGIN;
SAVEPOINT sp;
CREATE TEMP TABLE foo(bar int4);
ROLLBACK TO sp;
PREPARE TRANSACTION 'savepointTestCase_archives.postgresql.org_pgsql-hackers_2008-03_msg00017.php';
COMMIT PREPARED 'savepointTestCase_archives.postgresql.org_pgsql-hackers_2008-03_msg00017.php';

\echo Dirty buffer check
begin;
create temp table foo(a int, b int, c int) on commit drop;
select relname, relfilenode from pg_class where relname='foo';
insert into foo values(1,1,1);
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
insert into foo select * from foo;
prepare transaction 'bcd';
commit prepared 'bcd';
begin;
create temp table bar(a int, b int, c int) on commit drop;
select relname, relfilenode from pg_class where relname='bar';
insert into bar values(1,1,1);
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
insert into bar select * from bar;
commit;

\echo Existing non-empty temp table at commit time should still fail
begin;
create temp table foo(x int);
insert into foo values(1);
prepare transaction 'existingTempTableShouldFail';
commit prepared 'existingTempTableShouldFail';


pgsql-hackers by date:

Previous
From: Grzegorz Jaskiewicz
Date:
Subject: query path, and rules
Next
From: Simon Riggs
Date:
Subject: Reducing some DDL Locks to ShareLock