Hackers,
This patch adds subtransaction support into the storage manager. Files
created or dropped inside a subtransaction are correctly dealt with at
subtransaction commit or abort.
This works:
create table foo (a int);
create table foo2 (a int);
begin;
begin;
create table bar (a int);
select relfilenode, relname from pg_class where relname in ('foo',
'bar');
drop table foo2;
rollback;
drop table foo;
create table baz (a int);
select relfilenode, relname from pg_class where relname='baz';
commit;
At this point, the files for "bar" and "foo" have disappeared, while
"foo2" and "baz" remain. (Note however that the catalog entries are not
correct -- this is because we don't have correctly recorded results in
pg_clog.)
While making this I realized I had made a mistake regarding portal
memory, so I also correct it with this patch. As a side effect, the
following works;
begin;
begin;
declare foo cursor for select 1;
commit;
begin;
declare bar cursor for select 1;
rollback;
fetch all from foo; -- returns 1 row
fetch all from bar; -- no such cursor
rollback;
(This patch will only apply cleanly with the previous patch applied.)
Still missing:
- support for prepared statements, async notifies. Easy.
- support for on commit actions. Not sure.
- support for deferred triggers. Not so easy, maybe hard.
- correct LWLock handling. Should be easy (release them all on abort)
- correct regular lock handling. Not so easy.
- pg_clog/pg_subtrans. Need a solution.
PS: somehow I managed to get tired of the phrase "nested transactions"
and I'm using the term "subtransactions" instead. In my head they are
the same thing ...
--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
Hi! I'm a .signature virus!
cp me into your .signature file to help me spread!
diff -cr --exclude-from=diff-ignore 08simple/src/backend/access/transam/xact.c
10smgr/src/backend/access/transam/xact.c
*** 08simple/src/backend/access/transam/xact.c 2004-04-20 01:25:52.000000000 -0400
--- 10smgr/src/backend/access/transam/xact.c 2004-04-25 13:34:13.376961237 -0400
***************
*** 202,207 ****
--- 202,208 ----
static void AtSubStart_Memory(void);
static void AtSubCommit_Memory(void);
+ static void AtSubAbort_Memory(void);
static void AtSubCleanup_Memory(void);
#else /* SUBTRANSACTIONS */
***************
*** 382,387 ****
--- 383,434 ----
/*
+ * TransactionGetPendingDeletes
+ */
+ List *
+ TransactionGetPendingDeletes(void)
+ {
+ TransactionState s = CurrentTransactionState;
+
+ return s->pendingDeletes;
+ }
+
+ /*
+ * TransactionSetPendingDeletes
+ */
+ void
+ TransactionSetPendingDeletes(List *pending)
+ {
+ TransactionState s = CurrentTransactionState;
+
+ s->pendingDeletes = pending;
+ }
+
+ /*
+ * TransactionGetParentPendingDeletes
+ */
+ List *
+ TransactionGetParentPendingDeletes(void)
+ {
+ TransactionState s = CurrentTransactionState;
+
+ Assert(s->parent != NULL);
+ return s->parent->pendingDeletes;
+ }
+
+ /*
+ * TransactionSetParentPendingDeletes
+ */
+ void
+ TransactionSetParentPendingDeletes(List *pending)
+ {
+ TransactionState s = CurrentTransactionState;
+
+ Assert(s->parent != NULL);
+ s->parent->pendingDeletes = pending;
+ }
+
+ /*
* TransactionIdIsCurrentTransactionId
*
* During bootstrap, we cheat and say "it's not my transaction ID" even though
***************
*** 479,484 ****
--- 526,533 ----
static void
AtStart_Memory(void)
{
+ TransactionState s = CurrentTransactionState;
+
/*
* We shouldn't have a transaction context already.
*/
***************
*** 507,512 ****
--- 556,563 ----
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
+
+ s->commitContext = CommitContext;
}
#ifdef SUBTRANSACTIONS
***************
*** 537,542 ****
--- 588,595 ----
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
+
+ s->commitContext = CommitContext;
}
#endif /* SUBTRANSACTIONS */
***************
*** 884,889 ****
--- 937,954 ----
MemoryContextSwitchTo(TopMemoryContext);
}
+ #ifdef SUBTRANSACTIONS
+ /*
+ * AtSubAbort_Memory
+ */
+ static void
+ AtSubAbort_Memory(void)
+ {
+ Assert(TopTransactionContext != NULL);
+
+ MemoryContextSwitchTo(TopTransactionContext);
+ }
+ #endif
/* ----------------------------------------------------------------
* CleanupTransaction stuff
***************
*** 1002,1007 ****
--- 1067,1073 ----
* progress"
*/
s->state = TRANS_INPROGRESS;
+ ShowTransactionState("StartTransaction");
}
***************
*** 1013,1018 ****
--- 1079,1086 ----
{
TransactionState s = CurrentTransactionState;
+ ShowTransactionState("CommitTransaction");
+
/*
* check the current transaction state
*/
***************
*** 1267,1274 ****
{
TransactionState s = CurrentTransactionState;
- ShowTransactionState("StartTransactionCommand");
-
switch (s->blockState)
{
/*
--- 1335,1340 ----
***************
*** 1321,1327 ****
*/
Assert(TopTransactionContext != NULL);
MemoryContextSwitchTo(TopTransactionContext);
- ShowTransactionState("End StartTransactionCommand");
}
/*
--- 1387,1392 ----
***************
*** 1331,1337 ****
CommitTransactionCommand(void)
{
TransactionState s = CurrentTransactionState;
- ShowTransactionState("CommitTransactionCommand");
switch (s->blockState)
{
--- 1396,1401 ----
***************
*** 1471,1477 ****
}
break;
}
- ShowTransactionState("End CommitTransactionCommand");
}
/*
--- 1535,1540 ----
***************
*** 1482,1489 ****
{
TransactionState s = CurrentTransactionState;
- ShowTransactionState("AbortCurrentTransaction");
-
switch (s->blockState)
{
/*
--- 1545,1550 ----
***************
*** 1599,1605 ****
AbortTransaction();
break;
}
- ShowTransactionState("End AbortCurrentTransaction");
}
/*
--- 1660,1665 ----
***************
*** 2134,2144 ****
void
StartSubTransaction(void)
{
! ShowTransactionState("StartSubTransaction");
/* Initialize the memory subsystem */
AtSubStart_Memory();
}
/*
--- 2194,2212 ----
void
StartSubTransaction(void)
{
! TransactionState s = CurrentTransactionState;
!
! /*
! * generate a new transaction id
! */
! s->transactionIdData = GetNewTransactionId();
!
! CommandCounterIncrement();
/* Initialize the memory subsystem */
AtSubStart_Memory();
+ ShowTransactionState("StartSubTransaction");
}
/*
***************
*** 2147,2154 ****
--- 2215,2230 ----
void
CommitSubTransaction(void)
{
+ TransactionState s = CurrentTransactionState;
ShowTransactionState("CommitSubTransaction");
+ CommandCounterIncrement();
+
+ Assert(s->parent != NULL);
+
+ smgrCommitSubTransaction();
+
+ AtSubCommit_Portals(s->parent->transactionIdData);
AtSubCommit_Memory();
}
***************
*** 2159,2164 ****
--- 2235,2248 ----
AbortSubTransaction(void)
{
ShowTransactionState("AbortSubTransaction");
+
+ AtSubAbort_Portals();
+ AtSubAbort_Memory();
+
+ /* I'm not sure this is needed. */
+ CommandCounterIncrement();
+
+ smgrAbortSubTransaction();
}
/*
***************
*** 2169,2174 ****
--- 2253,2259 ----
{
ShowTransactionState("CleanupSubTransaction");
+ AtSubCleanup_Portals();
AtSubCleanup_Memory();
}
***************
*** 2186,2201 ****
* Make sure the transaction state node is in a long-lived context
*/
old_cxt = MemoryContextSwitchTo(TopMemoryContext);
- parent = (TransactionState) palloc(sizeof(TransactionStateData));
- MemoryContextSwitchTo(old_cxt);
memcpy(parent, s, sizeof(TransactionStateData));
- parent->commitContext = CommitContext;
s->parent = parent;
s->nestingLevel = s->parent->nestingLevel + 1;
s->commandId = s->parent->commandId;
! CommandCounterIncrement();
}
/*
--- 2271,2285 ----
* Make sure the transaction state node is in a long-lived context
*/
old_cxt = MemoryContextSwitchTo(TopMemoryContext);
+ parent = (TransactionState) palloc(sizeof(TransactionStateData));
memcpy(parent, s, sizeof(TransactionStateData));
s->parent = parent;
s->nestingLevel = s->parent->nestingLevel + 1;
s->commandId = s->parent->commandId;
!
! MemoryContextSwitchTo(old_cxt);
}
/*
***************
*** 2215,2221 ****
memcpy(s, p, sizeof(TransactionStateData));
CommitContext = s->commitContext;
- CommandCounterIncrement();
/* Free the old parent structure */
pfree(p);
--- 2299,2304 ----
***************
*** 2227,2234 ****
void
ShowTransactionState(char *str)
{
elog(DEBUG2, "%s", str);
! ShowTransactionStateRec(CurrentTransactionState, 0);
}
/*
--- 2310,2319 ----
void
ShowTransactionState(char *str)
{
+ TransactionState s = CurrentTransactionState;
+
elog(DEBUG2, "%s", str);
! ShowTransactionStateRec(CurrentTransactionState, 2 * s->nestingLevel);
}
/*
***************
*** 2237,2262 ****
void
ShowTransactionStateRec(TransactionState s, int indent)
{
! char *i;
! char *blockState = BlockStateAsString(s->blockState);
! char *state = TransStateAsString(s->state);
!
! i = (char *)malloc(sizeof(char) * indent + 1);
! memset(i, ' ', indent);
! memset(i + indent, '\0', 1);
! elog(DEBUG1, "%sblockState: %s; state: %s, tid/cid: %u/%u, nestlvl: %d",
! i,
blockState,
state,
(unsigned int)s->transactionIdData,
! (unsigned int)s->commandId ,
s->nestingLevel);
! free(i);
pfree(blockState);
pfree(state);
- if (s->parent)
- ShowTransactionStateRec(s->parent, indent + 1);
}
#endif /* SUBTRANSACTIONS */
--- 2322,2348 ----
void
ShowTransactionStateRec(TransactionState s, int indent)
{
! char *blockState = BlockStateAsString(s->blockState);
! char *state = TransStateAsString(s->state);
! char *tmp;
!
! if (s->parent)
! ShowTransactionStateRec(s->parent, indent - 2);
! tmp = (char *)malloc(sizeof(char) * indent + 1);
! memset(tmp, ' ', indent);
! memset(tmp + indent, '\0', 1);
!
! elog(DEBUG2, "%sblockState: %s; state: %s, xid/cid: %u/%u, nestlvl: %d",
! tmp,
blockState,
state,
(unsigned int)s->transactionIdData,
! (unsigned int)s->commandId,
s->nestingLevel);
! free(tmp);
pfree(blockState);
pfree(state);
}
#endif /* SUBTRANSACTIONS */
diff -cr --exclude-from=diff-ignore 08simple/src/backend/storage/smgr/smgr.c
10smgr/src/backend/storage/smgr/smgr.c
*** 08simple/src/backend/storage/smgr/smgr.c 2004-04-19 17:10:12.000000000 -0400
--- 10smgr/src/backend/storage/smgr/smgr.c 2004-04-25 12:37:32.247548626 -0400
***************
*** 17,22 ****
--- 17,23 ----
*/
#include "postgres.h"
+ #include "access/xact.h"
#include "storage/bufmgr.h"
#include "storage/freespace.h"
#include "storage/ipc.h"
***************
*** 77,98 ****
* executed immediately, but is just entered in the list. When and if
* the transaction commits, we can delete the physical file.
*
! * NOTE: the list is kept in TopMemoryContext to be sure it won't disappear
! * unbetimes. It'd probably be OK to keep it in TopTransactionContext,
! * but I'm being paranoid.
*/
! typedef struct PendingRelDelete
{
RelFileNode relnode; /* relation that may need to be deleted */
int which; /* which storage manager? */
bool isTemp; /* is it a temporary relation? */
bool atCommit; /* T=delete at commit; F=delete at
abort */
! struct PendingRelDelete *next; /* linked-list link */
! } PendingRelDelete;
!
! static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
/*
* Declarations for smgr-related XLOG records
--- 78,95 ----
* executed immediately, but is just entered in the list. When and if
* the transaction commits, we can delete the physical file.
*
! * The list is kept in CommitContext.
*/
! typedef struct PendingRelDeleteData
{
RelFileNode relnode; /* relation that may need to be deleted */
int which; /* which storage manager? */
bool isTemp; /* is it a temporary relation? */
bool atCommit; /* T=delete at commit; F=delete at
abort */
! } PendingRelDeleteData;
+ typedef PendingRelDeleteData *PendingRelDelete;
/*
* Declarations for smgr-related XLOG records
***************
*** 300,306 ****
XLogRecPtr lsn;
XLogRecData rdata;
xl_smgr_create xlrec;
! PendingRelDelete *pending;
if (! (*(smgrsw[reln->smgr_which].smgr_create)) (reln, isRedo))
ereport(ERROR,
--- 297,304 ----
XLogRecPtr lsn;
XLogRecData rdata;
xl_smgr_create xlrec;
! PendingRelDelete pending;
! MemoryContext old_cxt;
if (! (*(smgrsw[reln->smgr_which].smgr_create)) (reln, isRedo))
ereport(ERROR,
***************
*** 326,340 ****
lsn = XLogInsert(RM_SMGR_ID, XLOG_SMGR_CREATE | XLOG_NO_TRAN, &rdata);
/* Add the relation to the list of stuff to delete at abort */
! pending = (PendingRelDelete *)
! MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = reln->smgr_rnode;
pending->which = reln->smgr_which;
pending->isTemp = isTemp;
pending->atCommit = false; /* delete if abort */
! pending->next = pendingDeletes;
! pendingDeletes = pending;
}
/*
--- 324,342 ----
lsn = XLogInsert(RM_SMGR_ID, XLOG_SMGR_CREATE | XLOG_NO_TRAN, &rdata);
+ old_cxt = MemoryContextSwitchTo(CommitContext);
+
/* Add the relation to the list of stuff to delete at abort */
! pending = (PendingRelDelete) palloc(sizeof(PendingRelDeleteData));
pending->relnode = reln->smgr_rnode;
pending->which = reln->smgr_which;
pending->isTemp = isTemp;
pending->atCommit = false; /* delete if abort */
!
! TransactionSetPendingDeletes(lcons(pending,
! TransactionGetPendingDeletes()));
!
! MemoryContextSwitchTo(old_cxt);
}
/*
***************
*** 348,364 ****
void
smgrscheduleunlink(SMgrRelation reln, bool isTemp)
{
! PendingRelDelete *pending;
/* Add the relation to the list of stuff to delete at commit */
! pending = (PendingRelDelete *)
! MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = reln->smgr_rnode;
pending->which = reln->smgr_which;
pending->isTemp = isTemp;
pending->atCommit = true; /* delete if commit */
! pending->next = pendingDeletes;
! pendingDeletes = pending;
/*
* NOTE: if the relation was created in this transaction, it will now
--- 350,369 ----
void
smgrscheduleunlink(SMgrRelation reln, bool isTemp)
{
! PendingRelDelete pending;
! MemoryContext old_cxt;
!
! old_cxt = MemoryContextSwitchTo(CommitContext);
/* Add the relation to the list of stuff to delete at commit */
! pending = (PendingRelDelete) palloc(sizeof(PendingRelDeleteData));
pending->relnode = reln->smgr_rnode;
pending->which = reln->smgr_which;
pending->isTemp = isTemp;
pending->atCommit = true; /* delete if commit */
!
! TransactionSetPendingDeletes(lcons(pending,
! TransactionGetPendingDeletes()));
/*
* NOTE: if the relation was created in this transaction, it will now
***************
*** 372,377 ****
--- 377,384 ----
/* Now close the file and throw away the hashtable entry */
smgrclose(reln);
+
+ MemoryContextSwitchTo(old_cxt);
}
/*
***************
*** 573,590 ****
void
smgrDoPendingDeletes(bool isCommit)
{
! while (pendingDeletes != NULL)
{
! PendingRelDelete *pending = pendingDeletes;
- pendingDeletes = pending->next;
if (pending->atCommit == isCommit)
smgr_internal_unlink(pending->relnode,
pending->which,
pending->isTemp,
false);
- pfree(pending);
}
}
/*
--- 580,640 ----
void
smgrDoPendingDeletes(bool isCommit)
{
! List *p;
!
! foreach (p, TransactionGetPendingDeletes())
{
! PendingRelDelete pending = lfirst(p);
if (pending->atCommit == isCommit)
smgr_internal_unlink(pending->relnode,
pending->which,
pending->isTemp,
false);
}
+
+ TransactionSetPendingDeletes(NIL);
+ }
+
+ /*
+ * smgrAbortSubTransaction() --- Take care of subtransaction abort.
+ *
+ * Delete created relations and forget about deleted relations.
+ * We don't care about these operations anymore because we know this
+ * subtransaction will not commit.
+ */
+ void
+ smgrAbortSubTransaction(void)
+ {
+ List *p;
+
+ foreach (p, TransactionGetPendingDeletes())
+ {
+ PendingRelDelete pending = lfirst(p);
+
+ if (pending->atCommit == false)
+ smgr_internal_unlink(pending->relnode,
+ pending->which,
+ pending->isTemp,
+ false);
+ }
+ }
+
+ /*
+ * smgrCommitSubTransaction() --- Take care of subtransaction commit.
+ *
+ * Reassign all items in the pending deletes list to the parent transaction.
+ */
+ void
+ smgrCommitSubTransaction(void)
+ {
+ List *pending,
+ *parentPending;
+
+ pending = TransactionGetPendingDeletes();
+ parentPending = TransactionGetParentPendingDeletes();
+
+ TransactionSetParentPendingDeletes(nconc(parentPending, pending));
}
/*
***************
*** 599,609 ****
{
int nrels;
RelFileNode *rptr;
! PendingRelDelete *pending;
nrels = 0;
! for (pending = pendingDeletes; pending != NULL; pending = pending->next)
{
if (pending->atCommit == forCommit)
nrels++;
}
--- 649,661 ----
{
int nrels;
RelFileNode *rptr;
! List *p;
nrels = 0;
! foreach (p, TransactionGetPendingDeletes())
{
+ PendingRelDelete pending = lfirst(p);
+
if (pending->atCommit == forCommit)
nrels++;
}
***************
*** 614,621 ****
}
rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode));
*ptr = rptr;
! for (pending = pendingDeletes; pending != NULL; pending = pending->next)
{
if (pending->atCommit == forCommit)
*rptr++ = pending->relnode;
}
--- 666,675 ----
}
rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode));
*ptr = rptr;
! foreach (p, TransactionGetPendingDeletes())
{
+ PendingRelDelete pending = lfirst(p);
+
if (pending->atCommit == forCommit)
*rptr++ = pending->relnode;
}
diff -cr --exclude-from=diff-ignore 08simple/src/backend/utils/mmgr/portalmem.c
10smgr/src/backend/utils/mmgr/portalmem.c
*** 08simple/src/backend/utils/mmgr/portalmem.c 2004-04-19 17:10:12.000000000 -0400
--- 10smgr/src/backend/utils/mmgr/portalmem.c 2004-04-25 12:47:46.014641484 -0400
***************
*** 511,513 ****
--- 511,603 ----
PortalDrop(portal, true);
}
}
+
+ #ifdef SUBTRANSACTIONS
+ /*
+ * Subtransaction commit handling for portals.
+ *
+ * Reassign all portals created by the current subtransaction to the
+ * parent transaction.
+ */
+ void
+ AtSubCommit_Portals(TransactionId parentXid)
+ {
+ HASH_SEQ_STATUS status;
+ PortalHashEnt *hentry;
+ TransactionId curXid = GetCurrentTransactionId();
+
+ hash_seq_init(&status, PortalHashTable);
+
+ while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Portal portal = hentry->portal;
+
+ if (portal->createXact == curXid)
+ portal->createXact = parentXid;
+ }
+ }
+
+ /*
+ * Subtransaction abort handling for portals.
+ *
+ * Deactivate all portals created during to the finishing subtransaction.
+ * Note that per AtSubCommit_Portals, this will catch portals created
+ * in descendents of the finishing subtransaction too.
+ */
+ void
+ AtSubAbort_Portals(void)
+ {
+ HASH_SEQ_STATUS status;
+ PortalHashEnt *hentry;
+ TransactionId curXid = GetCurrentTransactionId();
+
+ hash_seq_init(&status, PortalHashTable);
+
+ while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Portal portal = hentry->portal;
+
+ if (portal->createXact != curXid)
+ continue;
+
+ portal->portalActive = false;
+ if (PointerIsValid(portal->cleanup))
+ {
+ (*portal->cleanup) (portal, true);
+ portal->cleanup = NULL;
+ }
+ }
+ }
+
+ /*
+ * Subtransaction cleanup handling for portals.
+ *
+ * Drop all portals created in the aborting subtransaction and all
+ * its descendents.
+ */
+ void
+ AtSubCleanup_Portals(void)
+ {
+ HASH_SEQ_STATUS status;
+ PortalHashEnt *hentry;
+ TransactionId curXid = GetCurrentTransactionId();
+
+ hash_seq_init(&status, PortalHashTable);
+
+ while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Portal portal = hentry->portal;
+
+ if (portal->createXact != curXid)
+ continue;
+
+ /*
+ * Let's just make sure no one's active...
+ */
+ portal->portalActive = false;
+
+ /* Zap it with prejudice. */
+ PortalDrop(portal, true);
+ }
+ }
+ #endif /* SUBTRANSACTIONS */
diff -cr --exclude-from=diff-ignore 08simple/src/include/access/xact.h
10smgr/src/include/access/xact.h
*** 08simple/src/include/access/xact.h 2004-04-20 01:22:20.000000000 -0400
--- 10smgr/src/include/access/xact.h 2004-04-25 12:45:54.782938704 -0400
***************
*** 149,154 ****
--- 149,158 ----
extern CommandId GetCurrentCommandId(void);
extern AbsoluteTime GetCurrentTransactionStartTime(void);
extern AbsoluteTime GetCurrentTransactionStartTimeUsec(int *usec);
+ extern List *TransactionGetPendingDeletes(void);
+ extern void TransactionSetPendingDeletes(List *);
+ extern List *TransactionGetParentPendingDeletes(void);
+ extern void TransactionSetParentPendingDeletes(List *);
extern bool TransactionIdIsCurrentTransactionId(TransactionId xid);
extern bool CommandIdIsCurrentCommandId(CommandId cid);
extern void CommandCounterIncrement(void);
diff -cr --exclude-from=diff-ignore 08simple/src/include/storage/smgr.h
10smgr/src/include/storage/smgr.h
*** 08simple/src/include/storage/smgr.h 2004-04-19 17:10:13.000000000 -0400
--- 10smgr/src/include/storage/smgr.h 2004-04-25 12:37:33.222659985 -0400
***************
*** 61,66 ****
--- 61,68 ----
extern BlockNumber smgrnblocks(SMgrRelation reln);
extern BlockNumber smgrtruncate(SMgrRelation reln, BlockNumber nblocks);
extern void smgrDoPendingDeletes(bool isCommit);
+ extern void smgrAbortSubTransaction(void);
+ extern void smgrCommitSubTransaction(void);
extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr);
extern void smgrcommit(void);
extern void smgrabort(void);
diff -cr --exclude-from=diff-ignore 08simple/src/include/utils/portal.h
10smgr/src/include/utils/portal.h
*** 08simple/src/include/utils/portal.h 2004-04-19 17:10:13.000000000 -0400
--- 10smgr/src/include/utils/portal.h 2004-04-25 12:47:06.368113808 -0400
***************
*** 167,172 ****
--- 167,177 ----
extern void AtCommit_Portals(void);
extern void AtAbort_Portals(void);
extern void AtCleanup_Portals(void);
+ #ifdef SUBTRANSACTIONS
+ extern void AtSubCommit_Portals(TransactionId);
+ extern void AtSubAbort_Portals(void);
+ extern void AtSubCleanup_Portals(void);
+ #endif /* SUBTRANSACTIONS */
extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent);
extern Portal CreateNewPortal(void);
extern void PortalDrop(Portal portal, bool isError);
---------------------------(end of broadcast)---------------------------
TIP 7: don't forget to increase your free space map settings