mirror of
https://github.com/postgres/postgres.git
synced 2025-05-17 06:41:24 +03:00
3519 lines
90 KiB
C
3519 lines
90 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* xact.c
|
|
* top level transaction system support routines
|
|
*
|
|
* See src/backend/access/transam/README for more information.
|
|
*
|
|
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.177 2004/08/03 15:57:26 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "access/subtrans.h"
|
|
#include "access/xact.h"
|
|
#include "catalog/heap.h"
|
|
#include "catalog/index.h"
|
|
#include "catalog/namespace.h"
|
|
#include "commands/async.h"
|
|
#include "commands/tablecmds.h"
|
|
#include "commands/trigger.h"
|
|
#include "commands/user.h"
|
|
#include "executor/spi.h"
|
|
#include "libpq/be-fsstubs.h"
|
|
#include "miscadmin.h"
|
|
#include "storage/fd.h"
|
|
#include "storage/proc.h"
|
|
#include "storage/sinval.h"
|
|
#include "storage/smgr.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/inval.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/portal.h"
|
|
#include "utils/resowner.h"
|
|
#include "pgstat.h"
|
|
|
|
|
|
/*
|
|
* transaction states - transaction state from server perspective
|
|
*/
|
|
typedef enum TransState
|
|
{
|
|
TRANS_DEFAULT,
|
|
TRANS_START,
|
|
TRANS_INPROGRESS,
|
|
TRANS_COMMIT,
|
|
TRANS_ABORT
|
|
} TransState;
|
|
|
|
/*
|
|
* transaction block states - transaction state of client queries
|
|
*/
|
|
typedef enum TBlockState
|
|
{
|
|
/* not-in-transaction-block states */
|
|
TBLOCK_DEFAULT,
|
|
TBLOCK_STARTED,
|
|
|
|
/* transaction block states */
|
|
TBLOCK_BEGIN,
|
|
TBLOCK_INPROGRESS,
|
|
TBLOCK_END,
|
|
TBLOCK_ABORT,
|
|
TBLOCK_ENDABORT,
|
|
|
|
/* subtransaction states */
|
|
TBLOCK_SUBBEGIN,
|
|
TBLOCK_SUBINPROGRESS,
|
|
TBLOCK_SUBEND,
|
|
TBLOCK_SUBABORT,
|
|
TBLOCK_SUBABORT_PENDING,
|
|
TBLOCK_SUBENDABORT_ALL,
|
|
TBLOCK_SUBENDABORT_RELEASE,
|
|
TBLOCK_SUBENDABORT
|
|
} TBlockState;
|
|
|
|
/*
|
|
* transaction state structure
|
|
*/
|
|
typedef struct TransactionStateData
|
|
{
|
|
TransactionId transactionIdData; /* my XID */
|
|
char *name; /* savepoint name, if any */
|
|
int savepointLevel; /* savepoint level */
|
|
CommandId commandId; /* current CID */
|
|
TransState state; /* low-level state */
|
|
TBlockState blockState; /* high-level state */
|
|
int nestingLevel; /* nest depth */
|
|
MemoryContext curTransactionContext; /* my xact-lifetime context */
|
|
ResourceOwner curTransactionOwner; /* my query resources */
|
|
List *childXids; /* subcommitted child XIDs */
|
|
AclId currentUser; /* subxact start current_user */
|
|
bool prevXactReadOnly; /* entry-time xact r/o state */
|
|
struct TransactionStateData *parent; /* back link to parent */
|
|
} TransactionStateData;
|
|
|
|
typedef TransactionStateData *TransactionState;
|
|
|
|
/*
|
|
* childXids is currently implemented as an integer List, relying on the
|
|
* assumption that TransactionIds are no wider than int. We use these
|
|
* macros to provide some isolation in case that changes in the future.
|
|
*/
|
|
#define lfirst_xid(lc) ((TransactionId) lfirst_int(lc))
|
|
#define lappend_xid(list, datum) lappend_int(list, (int) (datum))
|
|
|
|
|
|
static void AbortTransaction(void);
|
|
static void AtAbort_Memory(void);
|
|
static void AtCleanup_Memory(void);
|
|
static void AtCommit_LocalCache(void);
|
|
static void AtCommit_Memory(void);
|
|
static void AtStart_Cache(void);
|
|
static void AtStart_Memory(void);
|
|
static void AtStart_ResourceOwner(void);
|
|
static void CallXactCallbacks(XactEvent event, TransactionId parentXid);
|
|
static void CleanupTransaction(void);
|
|
static void CommitTransaction(void);
|
|
static void RecordTransactionAbort(void);
|
|
static void StartTransaction(void);
|
|
|
|
static void RecordSubTransactionCommit(void);
|
|
static void StartSubTransaction(void);
|
|
static void CommitSubTransaction(void);
|
|
static void AbortSubTransaction(void);
|
|
static void CleanupSubTransaction(void);
|
|
static void StartAbortedSubTransaction(void);
|
|
static void PushTransaction(void);
|
|
static void PopTransaction(void);
|
|
static void CommitTransactionToLevel(int level);
|
|
static char *CleanupAbortedSubTransactions(bool returnName);
|
|
|
|
static void AtSubAbort_Memory(void);
|
|
static void AtSubCleanup_Memory(void);
|
|
static void AtSubCommit_Memory(void);
|
|
static void AtSubStart_Memory(void);
|
|
static void AtSubStart_ResourceOwner(void);
|
|
|
|
static void ShowTransactionState(const char *str);
|
|
static void ShowTransactionStateRec(TransactionState state);
|
|
static const char *BlockStateAsString(TBlockState blockState);
|
|
static const char *TransStateAsString(TransState state);
|
|
|
|
/*
|
|
* CurrentTransactionState always points to the current transaction state
|
|
* block. It will point to TopTransactionStateData when not in a
|
|
* transaction at all, or when in a top-level transaction.
|
|
*/
|
|
static TransactionStateData TopTransactionStateData = {
|
|
0, /* transaction id */
|
|
NULL, /* savepoint name */
|
|
0, /* savepoint level */
|
|
FirstCommandId, /* command id */
|
|
TRANS_DEFAULT, /* transaction state */
|
|
TBLOCK_DEFAULT, /* transaction block state from the client
|
|
* perspective */
|
|
0, /* nesting level */
|
|
NULL, /* cur transaction context */
|
|
NULL, /* cur transaction resource owner */
|
|
NIL, /* subcommitted child Xids */
|
|
0, /* entry-time current userid */
|
|
false, /* entry-time xact r/o state */
|
|
NULL /* link to parent state block */
|
|
};
|
|
|
|
static TransactionState CurrentTransactionState = &TopTransactionStateData;
|
|
|
|
/*
|
|
* These vars hold the value of now(), ie, the transaction start time.
|
|
* This does not change as we enter and exit subtransactions, so we don't
|
|
* keep it inside the TransactionState stack.
|
|
*/
|
|
static AbsoluteTime xactStartTime; /* integer part */
|
|
static int xactStartTimeUsec; /* microsecond part */
|
|
|
|
|
|
/*
|
|
* User-tweakable parameters
|
|
*/
|
|
int DefaultXactIsoLevel = XACT_READ_COMMITTED;
|
|
int XactIsoLevel;
|
|
|
|
bool DefaultXactReadOnly = false;
|
|
bool XactReadOnly;
|
|
|
|
int CommitDelay = 0; /* precommit delay in microseconds */
|
|
int CommitSiblings = 5; /* number of concurrent xacts needed to
|
|
* sleep */
|
|
|
|
|
|
/*
|
|
* List of add-on start- and end-of-xact callbacks
|
|
*/
|
|
typedef struct XactCallbackItem
|
|
{
|
|
struct XactCallbackItem *next;
|
|
XactCallback callback;
|
|
void *arg;
|
|
} XactCallbackItem;
|
|
|
|
static XactCallbackItem *Xact_callbacks = NULL;
|
|
|
|
static void (*_RollbackFunc) (void *) = NULL;
|
|
static void *_RollbackData = NULL;
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* transaction state accessors
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* IsTransactionState
|
|
*
|
|
* This returns true if we are currently running a query
|
|
* within an executing transaction.
|
|
*/
|
|
bool
|
|
IsTransactionState(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->state)
|
|
{
|
|
case TRANS_DEFAULT:
|
|
return false;
|
|
case TRANS_START:
|
|
return true;
|
|
case TRANS_INPROGRESS:
|
|
return true;
|
|
case TRANS_COMMIT:
|
|
return true;
|
|
case TRANS_ABORT:
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Shouldn't get here, but lint is not happy with this...
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* IsAbortedTransactionBlockState
|
|
*
|
|
* This returns true if we are currently running a query
|
|
* within an aborted transaction block.
|
|
*/
|
|
bool
|
|
IsAbortedTransactionBlockState(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->blockState == TBLOCK_ABORT ||
|
|
s->blockState == TBLOCK_SUBABORT)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetTopTransactionId
|
|
*
|
|
* Get the ID of the main transaction, even if we are currently inside
|
|
* a subtransaction.
|
|
*/
|
|
TransactionId
|
|
GetTopTransactionId(void)
|
|
{
|
|
return TopTransactionStateData.transactionIdData;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetCurrentTransactionId
|
|
*/
|
|
TransactionId
|
|
GetCurrentTransactionId(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
return s->transactionIdData;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetCurrentCommandId
|
|
*/
|
|
CommandId
|
|
GetCurrentCommandId(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
return s->commandId;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetCurrentTransactionStartTime
|
|
*/
|
|
AbsoluteTime
|
|
GetCurrentTransactionStartTime(void)
|
|
{
|
|
return xactStartTime;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetCurrentTransactionStartTimeUsec
|
|
*/
|
|
AbsoluteTime
|
|
GetCurrentTransactionStartTimeUsec(int *msec)
|
|
{
|
|
*msec = xactStartTimeUsec;
|
|
return xactStartTime;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetCurrentTransactionNestLevel
|
|
*
|
|
* Note: this will return zero when not inside any transaction, one when
|
|
* inside a top-level transaction, etc.
|
|
*/
|
|
int
|
|
GetCurrentTransactionNestLevel(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
return s->nestingLevel;
|
|
}
|
|
|
|
|
|
/*
|
|
* TransactionIdIsCurrentTransactionId
|
|
*
|
|
* During bootstrap, we cheat and say "it's not my transaction ID" even though
|
|
* it is. Along with transam.c's cheat to say that the bootstrap XID is
|
|
* already committed, this causes the tqual.c routines to see previously
|
|
* inserted tuples as committed, which is what we need during bootstrap.
|
|
*/
|
|
bool
|
|
TransactionIdIsCurrentTransactionId(TransactionId xid)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (AMI_OVERRIDE)
|
|
{
|
|
Assert(xid == BootstrapTransactionId);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* We will return true for the Xid of the current subtransaction,
|
|
* any of its subcommitted children, any of its parents, or any of
|
|
* their previously subcommitted children.
|
|
*/
|
|
while (s != NULL)
|
|
{
|
|
ListCell *cell;
|
|
|
|
if (TransactionIdEquals(xid, s->transactionIdData))
|
|
return true;
|
|
foreach(cell, s->childXids)
|
|
{
|
|
if (TransactionIdEquals(xid, lfirst_xid(cell)))
|
|
return true;
|
|
}
|
|
|
|
s = s->parent;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* CommandCounterIncrement
|
|
*/
|
|
void
|
|
CommandCounterIncrement(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
s->commandId += 1;
|
|
if (s->commandId == FirstCommandId) /* check for overflow */
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
errmsg("cannot have more than 2^32-1 commands in a transaction")));
|
|
|
|
/* Propagate new command ID into query snapshots, if set */
|
|
if (QuerySnapshot)
|
|
QuerySnapshot->curcid = s->commandId;
|
|
if (SerializableSnapshot)
|
|
SerializableSnapshot->curcid = s->commandId;
|
|
|
|
/*
|
|
* make cache changes visible to me.
|
|
*/
|
|
AtCommit_LocalCache();
|
|
AtStart_Cache();
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* StartTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* AtStart_Cache
|
|
*/
|
|
static void
|
|
AtStart_Cache(void)
|
|
{
|
|
AcceptInvalidationMessages();
|
|
}
|
|
|
|
/*
|
|
* AtStart_Memory
|
|
*/
|
|
static void
|
|
AtStart_Memory(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* We shouldn't have a transaction context already.
|
|
*/
|
|
Assert(TopTransactionContext == NULL);
|
|
|
|
/*
|
|
* Create a toplevel context for the transaction.
|
|
*/
|
|
TopTransactionContext =
|
|
AllocSetContextCreate(TopMemoryContext,
|
|
"TopTransactionContext",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
|
|
/*
|
|
* In a top-level transaction, CurTransactionContext is the same as
|
|
* TopTransactionContext.
|
|
*/
|
|
CurTransactionContext = TopTransactionContext;
|
|
s->curTransactionContext = CurTransactionContext;
|
|
|
|
/* Make the CurTransactionContext active. */
|
|
MemoryContextSwitchTo(CurTransactionContext);
|
|
}
|
|
|
|
/*
|
|
* AtStart_ResourceOwner
|
|
*/
|
|
static void
|
|
AtStart_ResourceOwner(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* We shouldn't have a transaction resource owner already.
|
|
*/
|
|
Assert(TopTransactionResourceOwner == NULL);
|
|
|
|
/*
|
|
* Create a toplevel resource owner for the transaction.
|
|
*/
|
|
s->curTransactionOwner = ResourceOwnerCreate(NULL, "TopTransaction");
|
|
|
|
TopTransactionResourceOwner = s->curTransactionOwner;
|
|
CurTransactionResourceOwner = s->curTransactionOwner;
|
|
CurrentResourceOwner = s->curTransactionOwner;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* StartSubTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* AtSubStart_Memory
|
|
*/
|
|
static void
|
|
AtSubStart_Memory(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
Assert(CurTransactionContext != NULL);
|
|
|
|
/*
|
|
* Create a CurTransactionContext, which will be used to hold data that
|
|
* survives subtransaction commit but disappears on subtransaction abort.
|
|
* We make it a child of the immediate parent's CurTransactionContext.
|
|
*/
|
|
CurTransactionContext = AllocSetContextCreate(CurTransactionContext,
|
|
"CurTransactionContext",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
s->curTransactionContext = CurTransactionContext;
|
|
|
|
/* Make the CurTransactionContext active. */
|
|
MemoryContextSwitchTo(CurTransactionContext);
|
|
}
|
|
|
|
/*
|
|
* AtSubStart_ResourceOwner
|
|
*/
|
|
static void
|
|
AtSubStart_ResourceOwner(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
Assert(s->parent != NULL);
|
|
|
|
/*
|
|
* Create a resource owner for the subtransaction. We make it a
|
|
* child of the immediate parent's resource owner.
|
|
*/
|
|
s->curTransactionOwner =
|
|
ResourceOwnerCreate(s->parent->curTransactionOwner,
|
|
"SubTransaction");
|
|
|
|
CurTransactionResourceOwner = s->curTransactionOwner;
|
|
CurrentResourceOwner = s->curTransactionOwner;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* CommitTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* RecordTransactionCommit
|
|
*/
|
|
void
|
|
RecordTransactionCommit(void)
|
|
{
|
|
int nrels;
|
|
RelFileNode *rptr;
|
|
int nchildren;
|
|
TransactionId *children;
|
|
|
|
/* Get data needed for commit record */
|
|
nrels = smgrGetPendingDeletes(true, &rptr);
|
|
nchildren = xactGetCommittedChildren(&children);
|
|
|
|
/*
|
|
* If we made neither any XLOG entries nor any temp-rel updates,
|
|
* and have no files to be deleted, we can omit recording the transaction
|
|
* commit at all. (This test includes the effects of subtransactions,
|
|
* so the presence of committed subxacts need not alone force a write.)
|
|
*/
|
|
if (MyXactMadeXLogEntry || MyXactMadeTempRelUpdate || nrels > 0)
|
|
{
|
|
TransactionId xid = GetCurrentTransactionId();
|
|
bool madeTCentries;
|
|
XLogRecPtr recptr;
|
|
|
|
/* Tell bufmgr and smgr to prepare for commit */
|
|
BufmgrCommit();
|
|
|
|
START_CRIT_SECTION();
|
|
|
|
/*
|
|
* We only need to log the commit in XLOG if the transaction made
|
|
* any transaction-controlled XLOG entries or will delete files.
|
|
* (If it made no transaction-controlled XLOG entries, its XID
|
|
* appears nowhere in permanent storage, so no one else will ever care
|
|
* if it committed.)
|
|
*/
|
|
madeTCentries = (MyLastRecPtr.xrecoff != 0);
|
|
if (madeTCentries || nrels > 0)
|
|
{
|
|
XLogRecData rdata[3];
|
|
int lastrdata = 0;
|
|
xl_xact_commit xlrec;
|
|
|
|
xlrec.xtime = time(NULL);
|
|
xlrec.nrels = nrels;
|
|
xlrec.nsubxacts = nchildren;
|
|
rdata[0].buffer = InvalidBuffer;
|
|
rdata[0].data = (char *) (&xlrec);
|
|
rdata[0].len = MinSizeOfXactCommit;
|
|
/* dump rels to delete */
|
|
if (nrels > 0)
|
|
{
|
|
rdata[0].next = &(rdata[1]);
|
|
rdata[1].buffer = InvalidBuffer;
|
|
rdata[1].data = (char *) rptr;
|
|
rdata[1].len = nrels * sizeof(RelFileNode);
|
|
lastrdata = 1;
|
|
}
|
|
/* dump committed child Xids */
|
|
if (nchildren > 0)
|
|
{
|
|
rdata[lastrdata].next = &(rdata[2]);
|
|
rdata[2].buffer = InvalidBuffer;
|
|
rdata[2].data = (char *) children;
|
|
rdata[2].len = nchildren * sizeof(TransactionId);
|
|
lastrdata = 2;
|
|
}
|
|
rdata[lastrdata].next = NULL;
|
|
|
|
recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT, rdata);
|
|
}
|
|
else
|
|
{
|
|
/* Just flush through last record written by me */
|
|
recptr = ProcLastRecEnd;
|
|
}
|
|
|
|
/*
|
|
* We must flush our XLOG entries to disk if we made any XLOG
|
|
* entries, whether in or out of transaction control. For
|
|
* example, if we reported a nextval() result to the client, this
|
|
* ensures that any XLOG record generated by nextval will hit the
|
|
* disk before we report the transaction committed.
|
|
*
|
|
* Note: if we generated a commit record above, MyXactMadeXLogEntry
|
|
* will certainly be set now.
|
|
*/
|
|
if (MyXactMadeXLogEntry)
|
|
{
|
|
/*
|
|
* Sleep before flush! So we can flush more than one commit
|
|
* records per single fsync. (The idea is some other backend
|
|
* may do the XLogFlush while we're sleeping. This needs work
|
|
* still, because on most Unixen, the minimum select() delay
|
|
* is 10msec or more, which is way too long.)
|
|
*
|
|
* We do not sleep if enableFsync is not turned on, nor if there
|
|
* are fewer than CommitSiblings other backends with active
|
|
* transactions.
|
|
*/
|
|
if (CommitDelay > 0 && enableFsync &&
|
|
CountActiveBackends() >= CommitSiblings)
|
|
pg_usleep(CommitDelay);
|
|
|
|
XLogFlush(recptr);
|
|
}
|
|
|
|
/*
|
|
* We must mark the transaction committed in clog if its XID
|
|
* appears either in permanent rels or in local temporary rels. We
|
|
* test this by seeing if we made transaction-controlled entries
|
|
* *OR* local-rel tuple updates. Note that if we made only the
|
|
* latter, we have not emitted an XLOG record for our commit, and
|
|
* so in the event of a crash the clog update might be lost. This
|
|
* is okay because no one else will ever care whether we
|
|
* committed.
|
|
*/
|
|
if (madeTCentries || MyXactMadeTempRelUpdate)
|
|
{
|
|
TransactionIdCommit(xid);
|
|
/* to avoid race conditions, the parent must commit first */
|
|
TransactionIdCommitTree(nchildren, children);
|
|
}
|
|
|
|
END_CRIT_SECTION();
|
|
}
|
|
|
|
/* Break the chain of back-links in the XLOG records I output */
|
|
MyLastRecPtr.xrecoff = 0;
|
|
MyXactMadeXLogEntry = false;
|
|
MyXactMadeTempRelUpdate = false;
|
|
|
|
/* Show myself as out of the transaction in PGPROC array */
|
|
MyProc->logRec.xrecoff = 0;
|
|
|
|
/* And clean up local data */
|
|
if (rptr)
|
|
pfree(rptr);
|
|
if (children)
|
|
pfree(children);
|
|
}
|
|
|
|
|
|
/*
|
|
* AtCommit_LocalCache
|
|
*/
|
|
static void
|
|
AtCommit_LocalCache(void)
|
|
{
|
|
/*
|
|
* Make catalog changes visible to me for the next command.
|
|
*/
|
|
CommandEndInvalidationMessages();
|
|
}
|
|
|
|
/*
|
|
* AtCommit_Memory
|
|
*/
|
|
static void
|
|
AtCommit_Memory(void)
|
|
{
|
|
/*
|
|
* Now that we're "out" of a transaction, have the system allocate
|
|
* things in the top memory context instead of per-transaction
|
|
* contexts.
|
|
*/
|
|
MemoryContextSwitchTo(TopMemoryContext);
|
|
|
|
/*
|
|
* Release all transaction-local memory.
|
|
*/
|
|
Assert(TopTransactionContext != NULL);
|
|
MemoryContextDelete(TopTransactionContext);
|
|
TopTransactionContext = NULL;
|
|
CurTransactionContext = NULL;
|
|
CurrentTransactionState->curTransactionContext = NULL;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* CommitSubTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* AtSubCommit_Memory
|
|
*
|
|
* We do not throw away the child's CurTransactionContext, since the data
|
|
* it contains will be needed at upper commit.
|
|
*/
|
|
static void
|
|
AtSubCommit_Memory(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
Assert(s->parent != NULL);
|
|
|
|
/* Return to parent transaction level's memory context. */
|
|
CurTransactionContext = s->parent->curTransactionContext;
|
|
MemoryContextSwitchTo(CurTransactionContext);
|
|
}
|
|
|
|
/*
|
|
* AtSubCommit_childXids
|
|
*
|
|
* Pass my own XID and my child XIDs up to my parent as committed children.
|
|
*/
|
|
static void
|
|
AtSubCommit_childXids(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
MemoryContext old_cxt;
|
|
|
|
Assert(s->parent != NULL);
|
|
|
|
old_cxt = MemoryContextSwitchTo(s->parent->curTransactionContext);
|
|
|
|
s->parent->childXids = lappend_xid(s->parent->childXids,
|
|
s->transactionIdData);
|
|
|
|
s->parent->childXids = list_concat(s->parent->childXids, s->childXids);
|
|
s->childXids = NIL; /* ensure list not doubly referenced */
|
|
|
|
MemoryContextSwitchTo(old_cxt);
|
|
}
|
|
|
|
/*
|
|
* RecordSubTransactionCommit
|
|
*/
|
|
static void
|
|
RecordSubTransactionCommit(void)
|
|
{
|
|
/*
|
|
* We do not log the subcommit in XLOG; it doesn't matter until
|
|
* the top-level transaction commits.
|
|
*
|
|
* We must mark the subtransaction subcommitted in clog if its XID
|
|
* appears either in permanent rels or in local temporary rels. We
|
|
* test this by seeing if we made transaction-controlled entries
|
|
* *OR* local-rel tuple updates. (The test here actually covers the
|
|
* entire transaction tree so far, so it may mark subtransactions that
|
|
* don't really need it, but it's probably not worth being tenser.
|
|
* Note that if a prior subtransaction dirtied these variables, then
|
|
* RecordTransactionCommit will have to do the full pushup anyway...)
|
|
*/
|
|
if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate)
|
|
{
|
|
TransactionId xid = GetCurrentTransactionId();
|
|
|
|
/* XXX does this really need to be a critical section? */
|
|
START_CRIT_SECTION();
|
|
|
|
/* Record subtransaction subcommit */
|
|
TransactionIdSubCommit(xid);
|
|
|
|
END_CRIT_SECTION();
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* AbortTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* RecordTransactionAbort
|
|
*/
|
|
static void
|
|
RecordTransactionAbort(void)
|
|
{
|
|
int nrels;
|
|
RelFileNode *rptr;
|
|
int nchildren;
|
|
TransactionId *children;
|
|
|
|
/* Get data needed for abort record */
|
|
nrels = smgrGetPendingDeletes(false, &rptr);
|
|
nchildren = xactGetCommittedChildren(&children);
|
|
|
|
/*
|
|
* If we made neither any transaction-controlled XLOG entries nor any
|
|
* temp-rel updates, and are not going to delete any files, we can omit
|
|
* recording the transaction abort at all. No one will ever care that
|
|
* it aborted. (These tests cover our whole transaction tree.)
|
|
*/
|
|
if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate || nrels > 0)
|
|
{
|
|
TransactionId xid = GetCurrentTransactionId();
|
|
|
|
/*
|
|
* Catch the scenario where we aborted partway through
|
|
* RecordTransactionCommit ...
|
|
*/
|
|
if (TransactionIdDidCommit(xid))
|
|
elog(PANIC, "cannot abort transaction %u, it was already committed", xid);
|
|
|
|
START_CRIT_SECTION();
|
|
|
|
/*
|
|
* We only need to log the abort in XLOG if the transaction made
|
|
* any transaction-controlled XLOG entries or will delete files.
|
|
* (If it made no transaction-controlled XLOG entries, its XID
|
|
* appears nowhere in permanent storage, so no one else will ever care
|
|
* if it committed.)
|
|
*
|
|
* We do not flush XLOG to disk unless deleting files, since the
|
|
* default assumption after a crash would be that we aborted, anyway.
|
|
*/
|
|
if (MyLastRecPtr.xrecoff != 0 || nrels > 0)
|
|
{
|
|
XLogRecData rdata[3];
|
|
int lastrdata = 0;
|
|
xl_xact_abort xlrec;
|
|
XLogRecPtr recptr;
|
|
|
|
xlrec.xtime = time(NULL);
|
|
xlrec.nrels = nrels;
|
|
xlrec.nsubxacts = nchildren;
|
|
rdata[0].buffer = InvalidBuffer;
|
|
rdata[0].data = (char *) (&xlrec);
|
|
rdata[0].len = MinSizeOfXactAbort;
|
|
/* dump rels to delete */
|
|
if (nrels > 0)
|
|
{
|
|
rdata[0].next = &(rdata[1]);
|
|
rdata[1].buffer = InvalidBuffer;
|
|
rdata[1].data = (char *) rptr;
|
|
rdata[1].len = nrels * sizeof(RelFileNode);
|
|
lastrdata = 1;
|
|
}
|
|
/* dump committed child Xids */
|
|
if (nchildren > 0)
|
|
{
|
|
rdata[lastrdata].next = &(rdata[2]);
|
|
rdata[2].buffer = InvalidBuffer;
|
|
rdata[2].data = (char *) children;
|
|
rdata[2].len = nchildren * sizeof(TransactionId);
|
|
lastrdata = 2;
|
|
}
|
|
rdata[lastrdata].next = NULL;
|
|
|
|
recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, rdata);
|
|
|
|
/* Must flush if we are deleting files... */
|
|
if (nrels > 0)
|
|
XLogFlush(recptr);
|
|
}
|
|
|
|
/*
|
|
* Mark the transaction aborted in clog. This is not absolutely
|
|
* necessary but we may as well do it while we are here.
|
|
*
|
|
* The ordering here isn't critical but it seems best to mark the
|
|
* parent last. That reduces the chance that concurrent
|
|
* TransactionIdDidAbort calls will decide they need to do redundant
|
|
* work.
|
|
*/
|
|
TransactionIdAbortTree(nchildren, children);
|
|
TransactionIdAbort(xid);
|
|
|
|
END_CRIT_SECTION();
|
|
}
|
|
|
|
/* Break the chain of back-links in the XLOG records I output */
|
|
MyLastRecPtr.xrecoff = 0;
|
|
MyXactMadeXLogEntry = false;
|
|
MyXactMadeTempRelUpdate = false;
|
|
|
|
/* Show myself as out of the transaction in PGPROC array */
|
|
MyProc->logRec.xrecoff = 0;
|
|
|
|
/* And clean up local data */
|
|
if (rptr)
|
|
pfree(rptr);
|
|
if (children)
|
|
pfree(children);
|
|
}
|
|
|
|
/*
|
|
* AtAbort_Memory
|
|
*/
|
|
static void
|
|
AtAbort_Memory(void)
|
|
{
|
|
/*
|
|
* Make sure we are in a valid context (not a child of
|
|
* TopTransactionContext...). Note that it is possible for this code
|
|
* to be called when we aren't in a transaction at all; go directly to
|
|
* TopMemoryContext in that case.
|
|
*/
|
|
if (TopTransactionContext != NULL)
|
|
{
|
|
MemoryContextSwitchTo(TopTransactionContext);
|
|
|
|
/*
|
|
* We do not want to destroy the transaction's global state yet,
|
|
* so we can't free any memory here.
|
|
*/
|
|
}
|
|
else
|
|
MemoryContextSwitchTo(TopMemoryContext);
|
|
}
|
|
|
|
|
|
/*
|
|
* AtSubAbort_Memory
|
|
*/
|
|
static void
|
|
AtSubAbort_Memory(void)
|
|
{
|
|
Assert(TopTransactionContext != NULL);
|
|
|
|
MemoryContextSwitchTo(TopTransactionContext);
|
|
}
|
|
|
|
/*
|
|
* RecordSubTransactionAbort
|
|
*/
|
|
static void
|
|
RecordSubTransactionAbort(void)
|
|
{
|
|
int nrels;
|
|
RelFileNode *rptr;
|
|
TransactionId xid = GetCurrentTransactionId();
|
|
int nchildren;
|
|
TransactionId *children;
|
|
|
|
/* Get data needed for abort record */
|
|
nrels = smgrGetPendingDeletes(false, &rptr);
|
|
nchildren = xactGetCommittedChildren(&children);
|
|
|
|
/*
|
|
* If we made neither any transaction-controlled XLOG entries nor any
|
|
* temp-rel updates, and are not going to delete any files, we can omit
|
|
* recording the transaction abort at all. No one will ever care that
|
|
* it aborted. (These tests cover our whole transaction tree, and
|
|
* therefore may mark subxacts that don't really need it, but it's
|
|
* probably not worth being tenser.)
|
|
*
|
|
* In this case we needn't worry about marking subcommitted children as
|
|
* aborted, because they didn't mark themselves as subcommitted in the
|
|
* first place; see the optimization in RecordSubTransactionCommit.
|
|
*/
|
|
if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate || nrels > 0)
|
|
{
|
|
START_CRIT_SECTION();
|
|
|
|
/*
|
|
* We only need to log the abort in XLOG if the transaction made
|
|
* any transaction-controlled XLOG entries or will delete files.
|
|
*/
|
|
if (MyLastRecPtr.xrecoff != 0 || nrels > 0)
|
|
{
|
|
XLogRecData rdata[3];
|
|
int lastrdata = 0;
|
|
xl_xact_abort xlrec;
|
|
XLogRecPtr recptr;
|
|
|
|
xlrec.xtime = time(NULL);
|
|
xlrec.nrels = nrels;
|
|
xlrec.nsubxacts = nchildren;
|
|
rdata[0].buffer = InvalidBuffer;
|
|
rdata[0].data = (char *) (&xlrec);
|
|
rdata[0].len = MinSizeOfXactAbort;
|
|
/* dump rels to delete */
|
|
if (nrels > 0)
|
|
{
|
|
rdata[0].next = &(rdata[1]);
|
|
rdata[1].buffer = InvalidBuffer;
|
|
rdata[1].data = (char *) rptr;
|
|
rdata[1].len = nrels * sizeof(RelFileNode);
|
|
lastrdata = 1;
|
|
}
|
|
/* dump committed child Xids */
|
|
if (nchildren > 0)
|
|
{
|
|
rdata[lastrdata].next = &(rdata[2]);
|
|
rdata[2].buffer = InvalidBuffer;
|
|
rdata[2].data = (char *) children;
|
|
rdata[2].len = nchildren * sizeof(TransactionId);
|
|
lastrdata = 2;
|
|
}
|
|
rdata[lastrdata].next = NULL;
|
|
|
|
recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, rdata);
|
|
|
|
/* Must flush if we are deleting files... */
|
|
if (nrels > 0)
|
|
XLogFlush(recptr);
|
|
}
|
|
|
|
/*
|
|
* Mark the transaction aborted in clog. This is not absolutely
|
|
* necessary but we may as well do it while we are here.
|
|
*/
|
|
TransactionIdAbortTree(nchildren, children);
|
|
TransactionIdAbort(xid);
|
|
|
|
END_CRIT_SECTION();
|
|
}
|
|
|
|
/*
|
|
* We can immediately remove failed XIDs from PGPROC's cache of
|
|
* running child XIDs. It's easiest to do it here while we have the
|
|
* child XID array at hand, even though in the main-transaction
|
|
* case the equivalent work happens just after return from
|
|
* RecordTransactionAbort.
|
|
*/
|
|
XidCacheRemoveRunningXids(xid, nchildren, children);
|
|
|
|
/* And clean up local data */
|
|
if (rptr)
|
|
pfree(rptr);
|
|
if (children)
|
|
pfree(children);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* CleanupTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* AtCleanup_Memory
|
|
*/
|
|
static void
|
|
AtCleanup_Memory(void)
|
|
{
|
|
/*
|
|
* Now that we're "out" of a transaction, have the system allocate
|
|
* things in the top memory context instead of per-transaction
|
|
* contexts.
|
|
*/
|
|
MemoryContextSwitchTo(TopMemoryContext);
|
|
|
|
Assert(CurrentTransactionState->parent == NULL);
|
|
|
|
/*
|
|
* Release all transaction-local memory.
|
|
*/
|
|
if (TopTransactionContext != NULL)
|
|
MemoryContextDelete(TopTransactionContext);
|
|
TopTransactionContext = NULL;
|
|
CurTransactionContext = NULL;
|
|
CurrentTransactionState->curTransactionContext = NULL;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* CleanupSubTransaction stuff
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* AtSubCleanup_Memory
|
|
*/
|
|
static void
|
|
AtSubCleanup_Memory(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
Assert(s->parent != NULL);
|
|
|
|
/* Make sure we're not in an about-to-be-deleted context */
|
|
MemoryContextSwitchTo(s->parent->curTransactionContext);
|
|
CurTransactionContext = s->parent->curTransactionContext;
|
|
|
|
/*
|
|
* Delete the subxact local memory contexts. Its CurTransactionContext
|
|
* can go too (note this also kills CurTransactionContexts from any
|
|
* children of the subxact).
|
|
*/
|
|
MemoryContextDelete(s->curTransactionContext);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* interface routines
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* StartTransaction
|
|
*/
|
|
static void
|
|
StartTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* check the current transaction state
|
|
*/
|
|
if (s->state != TRANS_DEFAULT)
|
|
elog(WARNING, "StartTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
/*
|
|
* set the current transaction state information appropriately during
|
|
* start processing
|
|
*/
|
|
s->state = TRANS_START;
|
|
|
|
/*
|
|
* Make sure we've freed any old snapshot, and reset xact state variables
|
|
*/
|
|
FreeXactSnapshot();
|
|
XactIsoLevel = DefaultXactIsoLevel;
|
|
XactReadOnly = DefaultXactReadOnly;
|
|
|
|
/*
|
|
* must initialize resource-management stuff first
|
|
*/
|
|
AtStart_Memory();
|
|
AtStart_ResourceOwner();
|
|
|
|
/*
|
|
* generate a new transaction id
|
|
*/
|
|
s->transactionIdData = GetNewTransactionId(false);
|
|
|
|
XactLockTableInsert(s->transactionIdData);
|
|
|
|
/*
|
|
* set now()
|
|
*/
|
|
xactStartTime = GetCurrentAbsoluteTimeUsec(&(xactStartTimeUsec));
|
|
|
|
/*
|
|
* initialize current transaction state fields
|
|
*/
|
|
s->commandId = FirstCommandId;
|
|
s->nestingLevel = 1;
|
|
s->childXids = NIL;
|
|
|
|
/*
|
|
* You might expect to see "s->currentUser = GetUserId();" here, but
|
|
* you won't because it doesn't work during startup; the userid isn't
|
|
* set yet during a backend's first transaction start. We only use
|
|
* the currentUser field in sub-transaction state structs.
|
|
*
|
|
* prevXactReadOnly is also valid only in sub-transactions.
|
|
*/
|
|
|
|
/*
|
|
* initialize other subsystems for new transaction
|
|
*/
|
|
AtStart_Inval();
|
|
AtStart_Cache();
|
|
DeferredTriggerBeginXact();
|
|
|
|
/*
|
|
* done with start processing, set current transaction state to "in
|
|
* progress"
|
|
*/
|
|
s->state = TRANS_INPROGRESS;
|
|
|
|
ShowTransactionState("StartTransaction");
|
|
}
|
|
|
|
/*
|
|
* CommitTransaction
|
|
*/
|
|
static void
|
|
CommitTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
ShowTransactionState("CommitTransaction");
|
|
|
|
/*
|
|
* check the current transaction state
|
|
*/
|
|
if (s->state != TRANS_INPROGRESS)
|
|
elog(WARNING, "CommitTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
Assert(s->parent == NULL);
|
|
|
|
/*
|
|
* Tell the trigger manager that this transaction is about to be
|
|
* committed. He'll invoke all trigger deferred until XACT before we
|
|
* really start on committing the transaction.
|
|
*/
|
|
DeferredTriggerEndXact();
|
|
|
|
/*
|
|
* Similarly, let ON COMMIT management do its thing before we start to
|
|
* commit.
|
|
*/
|
|
PreCommit_on_commit_actions();
|
|
|
|
/* Prevent cancel/die interrupt while cleaning up */
|
|
HOLD_INTERRUPTS();
|
|
|
|
/*
|
|
* set the current transaction state information appropriately during
|
|
* the abort processing
|
|
*/
|
|
s->state = TRANS_COMMIT;
|
|
|
|
/*
|
|
* Do pre-commit processing (most of this stuff requires database
|
|
* access, and in fact could still cause an error...)
|
|
*/
|
|
|
|
AtCommit_Portals();
|
|
|
|
/* close large objects before lower-level cleanup */
|
|
AtEOXact_LargeObject(true);
|
|
|
|
/* NOTIFY commit must come before lower-level cleanup */
|
|
AtCommit_Notify();
|
|
|
|
/* Update the flat password file if we changed pg_shadow or pg_group */
|
|
/* This should be the last step before commit */
|
|
AtEOXact_UpdatePasswordFile(true);
|
|
|
|
/*
|
|
* Here is where we really truly commit.
|
|
*/
|
|
RecordTransactionCommit();
|
|
|
|
/*
|
|
* Let others know about no transaction in progress by me. Note that
|
|
* this must be done _before_ releasing locks we hold and _after_
|
|
* RecordTransactionCommit.
|
|
*
|
|
* LWLockAcquire(SInvalLock) is required: UPDATE with xid 0 is blocked by
|
|
* xid 1' UPDATE, xid 1 is doing commit while xid 2 gets snapshot - if
|
|
* xid 2' GetSnapshotData sees xid 1 as running then it must see xid 0
|
|
* as running as well or it will see two tuple versions - one deleted
|
|
* by xid 1 and one inserted by xid 0. See notes in GetSnapshotData.
|
|
*/
|
|
if (MyProc != NULL)
|
|
{
|
|
/* Lock SInvalLock because that's what GetSnapshotData uses. */
|
|
LWLockAcquire(SInvalLock, LW_EXCLUSIVE);
|
|
MyProc->xid = InvalidTransactionId;
|
|
MyProc->xmin = InvalidTransactionId;
|
|
|
|
/* Clear the subtransaction-XID cache too while holding the lock */
|
|
MyProc->subxids.nxids = 0;
|
|
MyProc->subxids.overflowed = false;
|
|
|
|
LWLockRelease(SInvalLock);
|
|
}
|
|
|
|
/*
|
|
* This is all post-commit cleanup. Note that if an error is raised
|
|
* here, it's too late to abort the transaction. This should be just
|
|
* noncritical resource releasing.
|
|
*
|
|
* The ordering of operations is not entirely random. The idea is:
|
|
* release resources visible to other backends (eg, files, buffer
|
|
* pins); then release locks; then release backend-local resources. We
|
|
* want to release locks at the point where any backend waiting for us
|
|
* will see our transaction as being fully cleaned up.
|
|
*
|
|
* Resources that can be associated with individual queries are
|
|
* handled by the ResourceOwner mechanism. The other calls here
|
|
* are for backend-wide state.
|
|
*/
|
|
|
|
smgrDoPendingDeletes(true);
|
|
/* smgrcommit already done */
|
|
|
|
CallXactCallbacks(XACT_EVENT_COMMIT, InvalidTransactionId);
|
|
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_BEFORE_LOCKS,
|
|
true, true);
|
|
|
|
/*
|
|
* Make catalog changes visible to all backends. This has to happen
|
|
* after relcache references are dropped (see comments for
|
|
* AtEOXact_RelationCache), but before locks are released (if anyone
|
|
* is waiting for lock on a relation we've modified, we want them to
|
|
* know about the catalog change before they start using the relation).
|
|
*/
|
|
AtEOXact_Inval(true);
|
|
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_LOCKS,
|
|
true, true);
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_AFTER_LOCKS,
|
|
true, true);
|
|
|
|
AtEOXact_GUC(true, false);
|
|
AtEOXact_SPI(true);
|
|
AtEOXact_on_commit_actions(true, s->transactionIdData);
|
|
AtEOXact_Namespace(true);
|
|
AtEOXact_Files();
|
|
pgstat_count_xact_commit();
|
|
|
|
CurrentResourceOwner = NULL;
|
|
ResourceOwnerDelete(TopTransactionResourceOwner);
|
|
s->curTransactionOwner = NULL;
|
|
CurTransactionResourceOwner = NULL;
|
|
TopTransactionResourceOwner = NULL;
|
|
|
|
AtCommit_Memory();
|
|
|
|
s->nestingLevel = 0;
|
|
s->childXids = NIL;
|
|
|
|
/*
|
|
* done with commit processing, set current transaction state back to
|
|
* default
|
|
*/
|
|
s->state = TRANS_DEFAULT;
|
|
|
|
RESUME_INTERRUPTS();
|
|
}
|
|
|
|
/*
|
|
* AbortTransaction
|
|
*/
|
|
static void
|
|
AbortTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/* Prevent cancel/die interrupt while cleaning up */
|
|
HOLD_INTERRUPTS();
|
|
|
|
/*
|
|
* Release any LW locks we might be holding as quickly as possible.
|
|
* (Regular locks, however, must be held till we finish aborting.)
|
|
* Releasing LW locks is critical since we might try to grab them
|
|
* again while cleaning up!
|
|
*/
|
|
LWLockReleaseAll();
|
|
|
|
/* Clean up buffer I/O and buffer context locks, too */
|
|
AbortBufferIO();
|
|
UnlockBuffers();
|
|
|
|
/*
|
|
* Also clean up any open wait for lock, since the lock manager will
|
|
* choke if we try to wait for another lock before doing this.
|
|
*/
|
|
LockWaitCancel();
|
|
|
|
/*
|
|
* check the current transaction state
|
|
*/
|
|
if (s->state != TRANS_INPROGRESS)
|
|
elog(WARNING, "AbortTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
Assert(s->parent == NULL);
|
|
|
|
/*
|
|
* set the current transaction state information appropriately during
|
|
* the abort processing
|
|
*/
|
|
s->state = TRANS_ABORT;
|
|
|
|
/* Make sure we are in a valid memory context */
|
|
AtAbort_Memory();
|
|
|
|
/*
|
|
* Reset user id which might have been changed transiently. We cannot
|
|
* use s->currentUser, but must get the session userid from miscinit.c.
|
|
*
|
|
* (Note: it is not necessary to restore session authorization here
|
|
* because that can only be changed via GUC, and GUC will take care of
|
|
* rolling it back if need be. However, an error within a SECURITY
|
|
* DEFINER function could send control here with the wrong current
|
|
* userid.)
|
|
*/
|
|
SetUserId(GetSessionUserId());
|
|
|
|
/*
|
|
* do abort processing
|
|
*/
|
|
DeferredTriggerAbortXact();
|
|
AtAbort_Portals();
|
|
AtEOXact_LargeObject(false); /* 'false' means it's abort */
|
|
AtAbort_Notify();
|
|
AtEOXact_UpdatePasswordFile(false);
|
|
|
|
/* Advertise the fact that we aborted in pg_clog. */
|
|
RecordTransactionAbort();
|
|
|
|
/*
|
|
* Let others know about no transaction in progress by me. Note that
|
|
* this must be done _before_ releasing locks we hold and _after_
|
|
* RecordTransactionAbort.
|
|
*/
|
|
if (MyProc != NULL)
|
|
{
|
|
/* Lock SInvalLock because that's what GetSnapshotData uses. */
|
|
LWLockAcquire(SInvalLock, LW_EXCLUSIVE);
|
|
MyProc->xid = InvalidTransactionId;
|
|
MyProc->xmin = InvalidTransactionId;
|
|
|
|
/* Clear the subtransaction-XID cache too while holding the lock */
|
|
MyProc->subxids.nxids = 0;
|
|
MyProc->subxids.overflowed = false;
|
|
|
|
LWLockRelease(SInvalLock);
|
|
}
|
|
|
|
/*
|
|
* Post-abort cleanup. See notes in CommitTransaction() concerning
|
|
* ordering.
|
|
*/
|
|
|
|
smgrDoPendingDeletes(false);
|
|
smgrabort();
|
|
|
|
CallXactCallbacks(XACT_EVENT_ABORT, InvalidTransactionId);
|
|
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_BEFORE_LOCKS,
|
|
false, true);
|
|
AtEOXact_Inval(false);
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_LOCKS,
|
|
false, true);
|
|
ResourceOwnerRelease(TopTransactionResourceOwner,
|
|
RESOURCE_RELEASE_AFTER_LOCKS,
|
|
false, true);
|
|
|
|
AtEOXact_GUC(false, false);
|
|
AtEOXact_SPI(false);
|
|
AtEOXact_on_commit_actions(false, s->transactionIdData);
|
|
AtEOXact_Namespace(false);
|
|
AtEOXact_Files();
|
|
pgstat_count_xact_rollback();
|
|
|
|
/*
|
|
* State remains TRANS_ABORT until CleanupTransaction().
|
|
*/
|
|
RESUME_INTERRUPTS();
|
|
}
|
|
|
|
/*
|
|
* CleanupTransaction
|
|
*/
|
|
static void
|
|
CleanupTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* State should still be TRANS_ABORT from AbortTransaction().
|
|
*/
|
|
if (s->state != TRANS_ABORT)
|
|
elog(FATAL, "CleanupTransaction: unexpected state %s",
|
|
TransStateAsString(s->state));
|
|
|
|
/*
|
|
* do abort cleanup processing
|
|
*/
|
|
AtCleanup_Portals(); /* now safe to release portal memory */
|
|
|
|
CurrentResourceOwner = NULL; /* and resource owner */
|
|
ResourceOwnerDelete(TopTransactionResourceOwner);
|
|
s->curTransactionOwner = NULL;
|
|
CurTransactionResourceOwner = NULL;
|
|
TopTransactionResourceOwner = NULL;
|
|
|
|
AtCleanup_Memory(); /* and transaction memory */
|
|
|
|
s->nestingLevel = 0;
|
|
s->childXids = NIL;
|
|
|
|
/*
|
|
* done with abort processing, set current transaction state back to
|
|
* default
|
|
*/
|
|
s->state = TRANS_DEFAULT;
|
|
}
|
|
|
|
/*
|
|
* StartTransactionCommand
|
|
*/
|
|
void
|
|
StartTransactionCommand(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* if we aren't in a transaction block, we just do our usual
|
|
* start transaction.
|
|
*/
|
|
case TBLOCK_DEFAULT:
|
|
StartTransaction();
|
|
s->blockState = TBLOCK_STARTED;
|
|
break;
|
|
|
|
/*
|
|
* This is the case when we are somewhere in a transaction block
|
|
* and about to start a new command. For now we do nothing
|
|
* but someday we may do command-local resource initialization.
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
break;
|
|
|
|
/*
|
|
* Here we are in the middle of a transaction block but one of
|
|
* the commands caused an abort so we do nothing but remain in
|
|
* the abort state. Eventually we will get to the "END
|
|
* TRANSACTION" which will set things straight.
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
break;
|
|
|
|
/* These cases are invalid. */
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBEND:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
case TBLOCK_ENDABORT:
|
|
elog(FATAL, "StartTransactionCommand: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We must switch to CurTransactionContext before returning. This is
|
|
* already done if we called StartTransaction, otherwise not.
|
|
*/
|
|
Assert(CurTransactionContext != NULL);
|
|
MemoryContextSwitchTo(CurTransactionContext);
|
|
}
|
|
|
|
/*
|
|
* CommitTransactionCommand
|
|
*/
|
|
void
|
|
CommitTransactionCommand(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* This shouldn't happen, because it means the previous
|
|
* StartTransactionCommand didn't set the STARTED state
|
|
* appropriately, or we didn't manage previous pending
|
|
* abort states.
|
|
*/
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
elog(FATAL, "CommitTransactionCommand: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
|
|
/*
|
|
* If we aren't in a transaction block, just do our usual
|
|
* transaction commit.
|
|
*/
|
|
case TBLOCK_STARTED:
|
|
CommitTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* This is the case right after we get a "BEGIN TRANSACTION"
|
|
* command, but the user hasn't done anything else yet, so we
|
|
* change to the "transaction block in progress" state and
|
|
* return.
|
|
*/
|
|
case TBLOCK_BEGIN:
|
|
s->blockState = TBLOCK_INPROGRESS;
|
|
break;
|
|
|
|
/*
|
|
* This is the case when we have finished executing a command
|
|
* someplace within a transaction block. We increment the
|
|
* command counter and return.
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
CommandCounterIncrement();
|
|
break;
|
|
|
|
/*
|
|
* This is the case when we just got the "END TRANSACTION"
|
|
* statement, so we commit the transaction and go back to the
|
|
* default state.
|
|
*/
|
|
case TBLOCK_END:
|
|
/* commit all open subtransactions */
|
|
if (s->nestingLevel > 1)
|
|
CommitTransactionToLevel(2);
|
|
s = CurrentTransactionState;
|
|
Assert(s->parent == NULL);
|
|
/* and now the outer transaction */
|
|
CommitTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* Here we are in the middle of a transaction block but one of
|
|
* the commands caused an abort so we do nothing but remain in
|
|
* the abort state. Eventually we will get to the "END
|
|
* TRANSACTION" which will set things straight.
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
break;
|
|
|
|
/*
|
|
* Here we were in an aborted transaction block which just
|
|
* processed the "END TRANSACTION" command from the user, so
|
|
* clean up and return to the default state.
|
|
*/
|
|
case TBLOCK_ENDABORT:
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* Ditto, but in a subtransaction. AbortOutOfAnyTransaction
|
|
* will do the dirty work.
|
|
*/
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
AbortOutOfAnyTransaction();
|
|
s = CurrentTransactionState; /* changed by AbortOutOfAnyTransaction */
|
|
/* AbortOutOfAnyTransaction sets the blockState */
|
|
break;
|
|
|
|
/*
|
|
* We were just issued a SAVEPOINT inside a transaction block.
|
|
* Start a subtransaction. (DefineSavepoint already
|
|
* did PushTransaction, so as to have someplace to put the
|
|
* SUBBEGIN state.)
|
|
*/
|
|
case TBLOCK_SUBBEGIN:
|
|
StartSubTransaction();
|
|
s->blockState = TBLOCK_SUBINPROGRESS;
|
|
break;
|
|
|
|
/*
|
|
* Inside a subtransaction, increment the command counter.
|
|
*/
|
|
case TBLOCK_SUBINPROGRESS:
|
|
CommandCounterIncrement();
|
|
break;
|
|
|
|
/*
|
|
* We were issued a RELEASE command, so we end the current
|
|
* subtransaction and return to the parent transaction.
|
|
*/
|
|
case TBLOCK_SUBEND:
|
|
CommitSubTransaction();
|
|
PopTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
break;
|
|
|
|
/*
|
|
* If we are in an aborted subtransaction, do nothing.
|
|
*/
|
|
case TBLOCK_SUBABORT:
|
|
break;
|
|
|
|
/*
|
|
* The current subtransaction is ending. Do the equivalent
|
|
* of a ROLLBACK TO followed by a RELEASE command.
|
|
*/
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
CleanupAbortedSubTransactions(false);
|
|
break;
|
|
|
|
/*
|
|
* The current subtransaction is ending due to a ROLLBACK
|
|
* TO command, so close all savepoints up to the target
|
|
* level. When finished, recreate the savepoint.
|
|
*/
|
|
case TBLOCK_SUBENDABORT:
|
|
{
|
|
char *name = CleanupAbortedSubTransactions(true);
|
|
|
|
Assert(PointerIsValid(name));
|
|
DefineSavepoint(name);
|
|
s = CurrentTransactionState; /* changed by DefineSavepoint */
|
|
pfree(name);
|
|
|
|
/* This is the same as TBLOCK_SUBBEGIN case */
|
|
AssertState(s->blockState == TBLOCK_SUBBEGIN);
|
|
StartSubTransaction();
|
|
s->blockState = TBLOCK_SUBINPROGRESS;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CleanupAbortedSubTransactions
|
|
*
|
|
* Helper function for CommitTransactionCommand. Aborts and cleans up
|
|
* dead subtransactions after a ROLLBACK TO command. Optionally returns
|
|
* the name of the last dead subtransaction so it can be reused to redefine
|
|
* the savepoint. (Caller is responsible for pfree'ing the result.)
|
|
*/
|
|
static char *
|
|
CleanupAbortedSubTransactions(bool returnName)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
char *name = NULL;
|
|
|
|
AssertState(PointerIsValid(s->parent));
|
|
Assert(s->parent->blockState == TBLOCK_SUBINPROGRESS ||
|
|
s->parent->blockState == TBLOCK_INPROGRESS ||
|
|
s->parent->blockState == TBLOCK_STARTED ||
|
|
s->parent->blockState == TBLOCK_SUBABORT_PENDING);
|
|
|
|
/*
|
|
* Abort everything up to the target level. The current
|
|
* subtransaction only needs cleanup. If we need to save the name,
|
|
* look for the last subtransaction in TBLOCK_SUBABORT_PENDING state.
|
|
*/
|
|
if (returnName && s->parent->blockState != TBLOCK_SUBABORT_PENDING)
|
|
name = MemoryContextStrdup(TopMemoryContext, s->name);
|
|
|
|
CleanupSubTransaction();
|
|
PopTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
|
|
while (s->blockState == TBLOCK_SUBABORT_PENDING)
|
|
{
|
|
AbortSubTransaction();
|
|
if (returnName && s->parent->blockState != TBLOCK_SUBABORT_PENDING)
|
|
name = MemoryContextStrdup(TopMemoryContext, s->name);
|
|
CleanupSubTransaction();
|
|
PopTransaction();
|
|
s = CurrentTransactionState;
|
|
}
|
|
|
|
AssertState(s->blockState == TBLOCK_SUBINPROGRESS ||
|
|
s->blockState == TBLOCK_INPROGRESS ||
|
|
s->blockState == TBLOCK_STARTED);
|
|
|
|
return name;
|
|
}
|
|
|
|
/*
|
|
* AbortCurrentTransaction
|
|
*/
|
|
void
|
|
AbortCurrentTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* we aren't in a transaction, so we do nothing.
|
|
*/
|
|
case TBLOCK_DEFAULT:
|
|
break;
|
|
|
|
/*
|
|
* if we aren't in a transaction block, we just do the basic
|
|
* abort & cleanup transaction.
|
|
*/
|
|
case TBLOCK_STARTED:
|
|
AbortTransaction();
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* If we are in TBLOCK_BEGIN it means something screwed up
|
|
* right after reading "BEGIN TRANSACTION" so we enter the
|
|
* abort state. Eventually an "END TRANSACTION" will fix
|
|
* things.
|
|
*/
|
|
case TBLOCK_BEGIN:
|
|
AbortTransaction();
|
|
s->blockState = TBLOCK_ABORT;
|
|
/* CleanupTransaction happens when we exit TBLOCK_ENDABORT */
|
|
break;
|
|
|
|
/*
|
|
* This is the case when we are somewhere in a transaction block
|
|
* and we've gotten a failure, so we abort the transaction and
|
|
* set up the persistent ABORT state. We will stay in ABORT
|
|
* until we get an "END TRANSACTION".
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
AbortTransaction();
|
|
s->blockState = TBLOCK_ABORT;
|
|
/* CleanupTransaction happens when we exit TBLOCK_ENDABORT */
|
|
break;
|
|
|
|
/*
|
|
* Here, the system was fouled up just after the user wanted
|
|
* to end the transaction block so we abort the transaction
|
|
* and return to the default state.
|
|
*/
|
|
case TBLOCK_END:
|
|
AbortTransaction();
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* Here, we are already in an aborted transaction state and
|
|
* are waiting for an "END TRANSACTION" to come along and lo
|
|
* and behold, we abort again! So we just remain in the abort
|
|
* state.
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
break;
|
|
|
|
/*
|
|
* Here we were in an aborted transaction block which just
|
|
* processed the "END TRANSACTION" command but somehow aborted
|
|
* again.. since we must have done the abort processing, we
|
|
* clean up and return to the default state.
|
|
*/
|
|
case TBLOCK_ENDABORT:
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
|
|
/*
|
|
* If we are just starting a subtransaction, put it
|
|
* in aborted state.
|
|
*/
|
|
case TBLOCK_SUBBEGIN:
|
|
StartAbortedSubTransaction();
|
|
s->blockState = TBLOCK_SUBABORT;
|
|
break;
|
|
|
|
case TBLOCK_SUBINPROGRESS:
|
|
AbortSubTransaction();
|
|
s->blockState = TBLOCK_SUBABORT;
|
|
break;
|
|
|
|
/*
|
|
* If we are aborting an ending transaction,
|
|
* we have to abort the parent transaction too.
|
|
*/
|
|
case TBLOCK_SUBEND:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
AbortSubTransaction();
|
|
CleanupSubTransaction();
|
|
PopTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
Assert(s->blockState != TBLOCK_SUBEND &&
|
|
s->blockState != TBLOCK_SUBENDABORT);
|
|
AbortCurrentTransaction();
|
|
break;
|
|
|
|
/*
|
|
* Same as above, except the Abort() was already done.
|
|
*/
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
CleanupSubTransaction();
|
|
PopTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
Assert(s->blockState != TBLOCK_SUBEND &&
|
|
s->blockState != TBLOCK_SUBENDABORT);
|
|
AbortCurrentTransaction();
|
|
break;
|
|
|
|
/*
|
|
* We are already aborting the whole transaction tree.
|
|
* Do nothing, CommitTransactionCommand will call
|
|
* AbortOutOfAnyTransaction and set things straight.
|
|
*/
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* PreventTransactionChain
|
|
*
|
|
* This routine is to be called by statements that must not run inside
|
|
* a transaction block, typically because they have non-rollback-able
|
|
* side effects or do internal commits.
|
|
*
|
|
* If we have already started a transaction block, issue an error; also issue
|
|
* an error if we appear to be running inside a user-defined function (which
|
|
* could issue more commands and possibly cause a failure after the statement
|
|
* completes). Subtransactions are verboten too.
|
|
*
|
|
* stmtNode: pointer to parameter block for statement; this is used in
|
|
* a very klugy way to determine whether we are inside a function.
|
|
* stmtType: statement type name for error messages.
|
|
*/
|
|
void
|
|
PreventTransactionChain(void *stmtNode, const char *stmtType)
|
|
{
|
|
/*
|
|
* xact block already started?
|
|
*/
|
|
if (IsTransactionBlock())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
|
/* translator: %s represents an SQL statement name */
|
|
errmsg("%s cannot run inside a transaction block",
|
|
stmtType)));
|
|
|
|
/*
|
|
* subtransaction?
|
|
*/
|
|
if (IsSubTransaction())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
|
/* translator: %s represents an SQL statement name */
|
|
errmsg("%s cannot run inside a subtransaction",
|
|
stmtType)));
|
|
|
|
/*
|
|
* Are we inside a function call? If the statement's parameter block
|
|
* was allocated in QueryContext, assume it is an interactive command.
|
|
* Otherwise assume it is coming from a function.
|
|
*/
|
|
if (!MemoryContextContains(QueryContext, stmtNode))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
|
/* translator: %s represents an SQL statement name */
|
|
errmsg("%s cannot be executed from a function", stmtType)));
|
|
|
|
/* If we got past IsTransactionBlock test, should be in default state */
|
|
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
|
|
CurrentTransactionState->blockState != TBLOCK_STARTED)
|
|
elog(FATAL, "cannot prevent transaction chain");
|
|
/* all okay */
|
|
}
|
|
|
|
/*
|
|
* RequireTransactionChain
|
|
*
|
|
* This routine is to be called by statements that must run inside
|
|
* a transaction block, because they have no effects that persist past
|
|
* transaction end (and so calling them outside a transaction block
|
|
* is presumably an error). DECLARE CURSOR is an example.
|
|
*
|
|
* If we appear to be running inside a user-defined function, we do not
|
|
* issue an error, since the function could issue more commands that make
|
|
* use of the current statement's results. Likewise subtransactions.
|
|
* Thus this is an inverse for PreventTransactionChain.
|
|
*
|
|
* stmtNode: pointer to parameter block for statement; this is used in
|
|
* a very klugy way to determine whether we are inside a function.
|
|
* stmtType: statement type name for error messages.
|
|
*/
|
|
void
|
|
RequireTransactionChain(void *stmtNode, const char *stmtType)
|
|
{
|
|
/*
|
|
* xact block already started?
|
|
*/
|
|
if (IsTransactionBlock())
|
|
return;
|
|
|
|
/*
|
|
* subtransaction?
|
|
*/
|
|
if (IsSubTransaction())
|
|
return;
|
|
|
|
/*
|
|
* Are we inside a function call? If the statement's parameter block
|
|
* was allocated in QueryContext, assume it is an interactive command.
|
|
* Otherwise assume it is coming from a function.
|
|
*/
|
|
if (!MemoryContextContains(QueryContext, stmtNode))
|
|
return;
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
|
|
/* translator: %s represents an SQL statement name */
|
|
errmsg("%s may only be used in transaction blocks",
|
|
stmtType)));
|
|
}
|
|
|
|
/*
|
|
* IsInTransactionChain
|
|
*
|
|
* This routine is for statements that need to behave differently inside
|
|
* a transaction block than when running as single commands. ANALYZE is
|
|
* currently the only example.
|
|
*
|
|
* stmtNode: pointer to parameter block for statement; this is used in
|
|
* a very klugy way to determine whether we are inside a function.
|
|
*/
|
|
bool
|
|
IsInTransactionChain(void *stmtNode)
|
|
{
|
|
/*
|
|
* Return true on same conditions that would make PreventTransactionChain
|
|
* error out
|
|
*/
|
|
if (IsTransactionBlock())
|
|
return true;
|
|
|
|
if (IsSubTransaction())
|
|
return true;
|
|
|
|
if (!MemoryContextContains(QueryContext, stmtNode))
|
|
return true;
|
|
|
|
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
|
|
CurrentTransactionState->blockState != TBLOCK_STARTED)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Register or deregister callback functions for start- and end-of-xact
|
|
* operations.
|
|
*
|
|
* These functions are intended for use by dynamically loaded modules.
|
|
* For built-in modules we generally just hardwire the appropriate calls
|
|
* (mainly because it's easier to control the order that way, where needed).
|
|
*
|
|
* At transaction end, the callback occurs post-commit or post-abort, so the
|
|
* callback functions can only do noncritical cleanup. At subtransaction
|
|
* start, the callback is called when the subtransaction has finished
|
|
* initializing.
|
|
*/
|
|
void
|
|
RegisterXactCallback(XactCallback callback, void *arg)
|
|
{
|
|
XactCallbackItem *item;
|
|
|
|
item = (XactCallbackItem *)
|
|
MemoryContextAlloc(TopMemoryContext, sizeof(XactCallbackItem));
|
|
item->callback = callback;
|
|
item->arg = arg;
|
|
item->next = Xact_callbacks;
|
|
Xact_callbacks = item;
|
|
}
|
|
|
|
void
|
|
UnregisterXactCallback(XactCallback callback, void *arg)
|
|
{
|
|
XactCallbackItem *item;
|
|
XactCallbackItem *prev;
|
|
|
|
prev = NULL;
|
|
for (item = Xact_callbacks; item; prev = item, item = item->next)
|
|
{
|
|
if (item->callback == callback && item->arg == arg)
|
|
{
|
|
if (prev)
|
|
prev->next = item->next;
|
|
else
|
|
Xact_callbacks = item->next;
|
|
pfree(item);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
CallXactCallbacks(XactEvent event, TransactionId parentXid)
|
|
{
|
|
XactCallbackItem *item;
|
|
|
|
for (item = Xact_callbacks; item; item = item->next)
|
|
{
|
|
(*item->callback) (event, parentXid, item->arg);
|
|
}
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* transaction block support
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* BeginTransactionBlock
|
|
* This executes a BEGIN command.
|
|
*/
|
|
void
|
|
BeginTransactionBlock(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* We are not inside a transaction block, so allow one
|
|
* to begin.
|
|
*/
|
|
case TBLOCK_STARTED:
|
|
s->blockState = TBLOCK_BEGIN;
|
|
break;
|
|
|
|
/*
|
|
* Already a transaction block in progress.
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
|
errmsg("there is already a transaction in progress")));
|
|
break;
|
|
|
|
/* These cases are invalid. Reject them altogether. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_ENDABORT:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
case TBLOCK_SUBEND:
|
|
elog(FATAL, "BeginTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* EndTransactionBlock
|
|
* This executes a COMMIT command.
|
|
*
|
|
* Since COMMIT may actually do a ROLLBACK, the result indicates what
|
|
* happened: TRUE for COMMIT, FALSE for ROLLBACK.
|
|
*/
|
|
bool
|
|
EndTransactionBlock(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
bool result = false;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* We are in a transaction block which should commit when we
|
|
* get to the upcoming CommitTransactionCommand() so we set the
|
|
* state to "END". CommitTransactionCommand() will recognize this
|
|
* and commit the transaction and return us to the default state.
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
s->blockState = TBLOCK_END;
|
|
result = true;
|
|
break;
|
|
|
|
/*
|
|
* We are in a transaction block which aborted. Since the
|
|
* AbortTransaction() was already done, we need only
|
|
* change to the special "END ABORT" state. The upcoming
|
|
* CommitTransactionCommand() will recognise this and then put us
|
|
* back in the default state.
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
s->blockState = TBLOCK_ENDABORT;
|
|
break;
|
|
|
|
/*
|
|
* Here we are inside an aborted subtransaction. Go to the "abort
|
|
* the whole tree" state so that CommitTransactionCommand() calls
|
|
* AbortOutOfAnyTransaction.
|
|
*/
|
|
case TBLOCK_SUBABORT:
|
|
s->blockState = TBLOCK_SUBENDABORT_ALL;
|
|
break;
|
|
|
|
case TBLOCK_STARTED:
|
|
/*
|
|
* here, the user issued COMMIT when not inside a
|
|
* transaction. Issue a WARNING and go to abort state. The
|
|
* upcoming call to CommitTransactionCommand() will then put us
|
|
* back into the default state.
|
|
*/
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
|
|
errmsg("there is no transaction in progress")));
|
|
AbortTransaction();
|
|
s->blockState = TBLOCK_ENDABORT;
|
|
break;
|
|
|
|
/* these cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_ENDABORT:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_SUBEND:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
elog(FATAL, "EndTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* UserAbortTransactionBlock
|
|
* This executes a ROLLBACK command.
|
|
*/
|
|
void
|
|
UserAbortTransactionBlock(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* We are inside a failed transaction block and we got an
|
|
* abort command from the user. Abort processing is already
|
|
* done, we just need to move to the ENDABORT state so we will
|
|
* end up in the default state after the upcoming
|
|
* CommitTransactionCommand().
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
s->blockState = TBLOCK_ENDABORT;
|
|
break;
|
|
|
|
/*
|
|
* We are inside a failed subtransaction and we got an
|
|
* abort command from the user. Abort processing is already
|
|
* done, so go to the "abort all" state and
|
|
* CommitTransactionCommand will call AbortOutOfAnyTransaction
|
|
* to set things straight.
|
|
*/
|
|
case TBLOCK_SUBABORT:
|
|
s->blockState = TBLOCK_SUBENDABORT_ALL;
|
|
break;
|
|
|
|
/*
|
|
* We are inside a transaction block and we got an abort
|
|
* command from the user, so we move to the ENDABORT state and
|
|
* do abort processing so we will end up in the default state
|
|
* after the upcoming CommitTransactionCommand().
|
|
*/
|
|
case TBLOCK_INPROGRESS:
|
|
AbortTransaction();
|
|
s->blockState = TBLOCK_ENDABORT;
|
|
break;
|
|
|
|
/*
|
|
* We are inside a subtransaction. Abort the current
|
|
* subtransaction and go to the "abort all" state, so
|
|
* CommitTransactionCommand will call AbortOutOfAnyTransaction
|
|
* to set things straight.
|
|
*/
|
|
case TBLOCK_SUBINPROGRESS:
|
|
AbortSubTransaction();
|
|
s->blockState = TBLOCK_SUBENDABORT_ALL;
|
|
break;
|
|
|
|
/*
|
|
* The user issued ABORT when not inside a transaction. Issue
|
|
* a WARNING and go to abort state. The upcoming call to
|
|
* CommitTransactionCommand() will then put us back into the
|
|
* default state.
|
|
*/
|
|
case TBLOCK_STARTED:
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
|
|
errmsg("there is no transaction in progress")));
|
|
AbortTransaction();
|
|
s->blockState = TBLOCK_ENDABORT;
|
|
break;
|
|
|
|
/* These cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_ENDABORT:
|
|
case TBLOCK_SUBEND:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
case TBLOCK_SUBBEGIN:
|
|
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* DefineSavepoint
|
|
* This executes a SAVEPOINT command.
|
|
*/
|
|
void
|
|
DefineSavepoint(char *name)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
/* Normal subtransaction start */
|
|
PushTransaction();
|
|
s = CurrentTransactionState; /* changed by push */
|
|
/*
|
|
* Note that we are allocating the savepoint name in the
|
|
* parent transaction's CurTransactionContext, since we
|
|
* don't yet have a transaction context for the new guy.
|
|
*/
|
|
s->name = MemoryContextStrdup(CurTransactionContext, name);
|
|
s->blockState = TBLOCK_SUBBEGIN;
|
|
break;
|
|
|
|
/* These cases are invalid. Reject them altogether. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_ENDABORT:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
case TBLOCK_SUBEND:
|
|
elog(FATAL, "DefineSavepoint: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ReleaseSavepoint
|
|
* This executes a RELEASE command.
|
|
*/
|
|
void
|
|
ReleaseSavepoint(List *options)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
TransactionState target = s;
|
|
char *name = NULL;
|
|
ListCell *cell;
|
|
|
|
/*
|
|
* Check valid block state transaction status.
|
|
*/
|
|
switch (s->blockState)
|
|
{
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_ABORT:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
break;
|
|
|
|
/*
|
|
* We are in a non-aborted subtransaction. This is
|
|
* the only valid case.
|
|
*/
|
|
case TBLOCK_SUBINPROGRESS:
|
|
break;
|
|
|
|
/* these cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_ENDABORT:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_SUBEND:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
elog(FATAL, "ReleaseSavepoint: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
foreach (cell, options)
|
|
{
|
|
DefElem *elem = lfirst(cell);
|
|
|
|
if (strcmp(elem->defname, "savepoint_name") == 0)
|
|
name = strVal(elem->arg);
|
|
}
|
|
|
|
Assert(PointerIsValid(name));
|
|
|
|
while (target != NULL)
|
|
{
|
|
if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
|
|
break;
|
|
target = target->parent;
|
|
}
|
|
|
|
if (!PointerIsValid(target))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
|
|
CommitTransactionToLevel(target->nestingLevel);
|
|
}
|
|
|
|
/*
|
|
* RollbackToSavepoint
|
|
* This executes a ROLLBACK TO <savepoint> command.
|
|
*/
|
|
void
|
|
RollbackToSavepoint(List *options)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
TransactionState target,
|
|
xact;
|
|
ListCell *cell;
|
|
char *name = NULL;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/*
|
|
* We can't rollback to a savepoint if there is no saveopint
|
|
* defined.
|
|
*/
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_INPROGRESS:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
break;
|
|
|
|
/*
|
|
* There is at least one savepoint, so proceed.
|
|
*/
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
/*
|
|
* Have to do AbortSubTransaction, but first check
|
|
* if this is the right subtransaction
|
|
*/
|
|
break;
|
|
|
|
/* these cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_ENDABORT:
|
|
case TBLOCK_SUBEND:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
case TBLOCK_SUBBEGIN:
|
|
elog(FATAL, "RollbackToSavepoint: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
foreach (cell, options)
|
|
{
|
|
DefElem *elem = lfirst(cell);
|
|
|
|
if (strcmp(elem->defname, "savepoint_name") == 0)
|
|
name = strVal(elem->arg);
|
|
}
|
|
|
|
Assert(PointerIsValid(name));
|
|
|
|
for (target = s; PointerIsValid(target); target = target->parent)
|
|
{
|
|
if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
|
|
break;
|
|
}
|
|
|
|
if (!PointerIsValid(target))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
|
|
/* disallow crossing savepoint level boundaries */
|
|
if (target->savepointLevel != s->savepointLevel)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
|
|
errmsg("no such savepoint")));
|
|
|
|
/*
|
|
* Abort the current subtransaction, if needed. We can't Cleanup the
|
|
* savepoint yet, so signal CommitTransactionCommand to do it and
|
|
* close all savepoints up to the target level.
|
|
*/
|
|
if (s->blockState == TBLOCK_SUBINPROGRESS)
|
|
AbortSubTransaction();
|
|
s->blockState = TBLOCK_SUBENDABORT;
|
|
|
|
/*
|
|
* Mark "abort pending" all subtransactions up to the target
|
|
* subtransaction. (Except the current subtransaction!)
|
|
*/
|
|
xact = CurrentTransactionState;
|
|
|
|
while (xact != target)
|
|
{
|
|
xact = xact->parent;
|
|
Assert(PointerIsValid(xact));
|
|
Assert(xact->blockState == TBLOCK_SUBINPROGRESS);
|
|
xact->blockState = TBLOCK_SUBABORT_PENDING;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* BeginInternalSubTransaction
|
|
* This is the same as DefineSavepoint except it allows TBLOCK_STARTED
|
|
* state, and therefore it can safely be used in a function that might
|
|
* be called when not inside a BEGIN block. Also, we automatically
|
|
* cycle through CommitTransactionCommand/StartTransactionCommand
|
|
* instead of expecting the caller to do it.
|
|
*
|
|
* Optionally, name can be NULL to create an unnamed savepoint.
|
|
*/
|
|
void
|
|
BeginInternalSubTransaction(char *name)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
/* Normal subtransaction start */
|
|
PushTransaction();
|
|
s = CurrentTransactionState; /* changed by push */
|
|
/*
|
|
* Note that we are allocating the savepoint name in the
|
|
* parent transaction's CurTransactionContext, since we
|
|
* don't yet have a transaction context for the new guy.
|
|
*/
|
|
if (name)
|
|
s->name = MemoryContextStrdup(CurTransactionContext, name);
|
|
s->blockState = TBLOCK_SUBBEGIN;
|
|
break;
|
|
|
|
/* These cases are invalid. Reject them altogether. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_ENDABORT:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
case TBLOCK_SUBEND:
|
|
elog(FATAL, "BeginInternalSubTransaction: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
CommitTransactionCommand();
|
|
StartTransactionCommand();
|
|
}
|
|
|
|
/*
|
|
* ReleaseCurrentSubTransaction
|
|
*
|
|
* RELEASE (ie, commit) the innermost subtransaction, regardless of its
|
|
* savepoint name (if any).
|
|
* NB: do NOT use CommitTransactionCommand/StartTransactionCommand with this.
|
|
*/
|
|
void
|
|
ReleaseCurrentSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->blockState != TBLOCK_SUBINPROGRESS)
|
|
elog(ERROR, "ReleaseCurrentSubTransaction: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
MemoryContextSwitchTo(CurTransactionContext);
|
|
CommitTransactionToLevel(GetCurrentTransactionNestLevel());
|
|
}
|
|
|
|
/*
|
|
* RollbackAndReleaseCurrentSubTransaction
|
|
*
|
|
* ROLLBACK and RELEASE (ie, abort) the innermost subtransaction, regardless
|
|
* of its savepoint name (if any).
|
|
* NB: do NOT use CommitTransactionCommand/StartTransactionCommand with this.
|
|
*/
|
|
void
|
|
RollbackAndReleaseCurrentSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
/* Must be in a subtransaction */
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
break;
|
|
|
|
/* these cases are invalid. */
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_END:
|
|
case TBLOCK_ENDABORT:
|
|
case TBLOCK_SUBEND:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
case TBLOCK_SUBBEGIN:
|
|
elog(FATAL, "RollbackAndReleaseCurrentSubTransaction: unexpected state %s",
|
|
BlockStateAsString(s->blockState));
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Abort the current subtransaction, if needed.
|
|
*/
|
|
if (s->blockState == TBLOCK_SUBINPROGRESS)
|
|
AbortSubTransaction();
|
|
s->blockState = TBLOCK_SUBENDABORT_RELEASE;
|
|
|
|
/* And clean it up, too */
|
|
CleanupAbortedSubTransactions(false);
|
|
}
|
|
|
|
/*
|
|
* AbortOutOfAnyTransaction
|
|
*
|
|
* This routine is provided for error recovery purposes. It aborts any
|
|
* active transaction or transaction block, leaving the system in a known
|
|
* idle state.
|
|
*/
|
|
void
|
|
AbortOutOfAnyTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
/*
|
|
* Get out of any transaction or nested transaction
|
|
*/
|
|
do {
|
|
switch (s->blockState)
|
|
{
|
|
case TBLOCK_DEFAULT:
|
|
/* Not in a transaction, do nothing */
|
|
break;
|
|
case TBLOCK_STARTED:
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_END:
|
|
/* In a transaction, so clean up */
|
|
AbortTransaction();
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_ENDABORT:
|
|
/* AbortTransaction already done, still need Cleanup */
|
|
CleanupTransaction();
|
|
s->blockState = TBLOCK_DEFAULT;
|
|
break;
|
|
case TBLOCK_SUBBEGIN:
|
|
/*
|
|
* We didn't get as far as starting the subxact, so there's
|
|
* nothing to abort. Just pop back to parent.
|
|
*/
|
|
PopTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
break;
|
|
case TBLOCK_SUBINPROGRESS:
|
|
case TBLOCK_SUBEND:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
/* In a subtransaction, so clean it up and abort parent too */
|
|
AbortSubTransaction();
|
|
CleanupSubTransaction();
|
|
PopTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
break;
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
/* As above, but AbortSubTransaction already done */
|
|
CleanupSubTransaction();
|
|
PopTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
break;
|
|
}
|
|
} while (s->blockState != TBLOCK_DEFAULT);
|
|
|
|
/* Should be out of all subxacts now */
|
|
Assert(s->parent == NULL);
|
|
}
|
|
|
|
/*
|
|
* CommitTransactionToLevel
|
|
*
|
|
* Commit everything from the current transaction level
|
|
* up to the specified level (inclusive).
|
|
*/
|
|
static void
|
|
CommitTransactionToLevel(int level)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
Assert(s->state == TRANS_INPROGRESS);
|
|
|
|
while (s->nestingLevel >= level)
|
|
{
|
|
CommitSubTransaction();
|
|
PopTransaction();
|
|
s = CurrentTransactionState; /* changed by pop */
|
|
Assert(s->state == TRANS_INPROGRESS);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* IsTransactionBlock --- are we within a transaction block?
|
|
*/
|
|
bool
|
|
IsTransactionBlock(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->blockState == TBLOCK_DEFAULT || s->blockState == TBLOCK_STARTED)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* IsTransactionOrTransactionBlock --- are we within either a transaction
|
|
* or a transaction block? (The backend is only really "idle" when this
|
|
* returns false.)
|
|
*
|
|
* This should match up with IsTransactionBlock and IsTransactionState.
|
|
*/
|
|
bool
|
|
IsTransactionOrTransactionBlock(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->blockState == TBLOCK_DEFAULT)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* TransactionBlockStatusCode - return status code to send in ReadyForQuery
|
|
*/
|
|
char
|
|
TransactionBlockStatusCode(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
switch (s->blockState)
|
|
{
|
|
case TBLOCK_DEFAULT:
|
|
case TBLOCK_STARTED:
|
|
return 'I'; /* idle --- not in transaction */
|
|
case TBLOCK_BEGIN:
|
|
case TBLOCK_INPROGRESS:
|
|
case TBLOCK_END:
|
|
case TBLOCK_SUBINPROGRESS:
|
|
case TBLOCK_SUBBEGIN:
|
|
case TBLOCK_SUBEND:
|
|
return 'T'; /* in transaction */
|
|
case TBLOCK_ABORT:
|
|
case TBLOCK_ENDABORT:
|
|
case TBLOCK_SUBABORT:
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
case TBLOCK_SUBENDABORT:
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
return 'E'; /* in failed transaction */
|
|
}
|
|
|
|
/* should never get here */
|
|
elog(FATAL, "invalid transaction block state: %s",
|
|
BlockStateAsString(s->blockState));
|
|
return 0; /* keep compiler quiet */
|
|
}
|
|
|
|
/*
|
|
* IsSubTransaction
|
|
*/
|
|
bool
|
|
IsSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->nestingLevel >= 2)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* StartSubTransaction
|
|
*/
|
|
static void
|
|
StartSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->state != TRANS_DEFAULT)
|
|
elog(WARNING, "StartSubTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
s->state = TRANS_START;
|
|
|
|
/*
|
|
* must initialize resource-management stuff first
|
|
*/
|
|
AtSubStart_Memory();
|
|
AtSubStart_ResourceOwner();
|
|
|
|
/*
|
|
* Generate a new Xid and record it in pg_subtrans. NB: we must make
|
|
* the subtrans entry BEFORE the Xid appears anywhere in shared storage,
|
|
* such as in the lock table; because until it's made the Xid may not
|
|
* appear to be "running" to other backends. See GetNewTransactionId.
|
|
*/
|
|
s->transactionIdData = GetNewTransactionId(true);
|
|
|
|
SubTransSetParent(s->transactionIdData, s->parent->transactionIdData);
|
|
|
|
XactLockTableInsert(s->transactionIdData);
|
|
|
|
/*
|
|
* Finish setup of other transaction state fields.
|
|
*/
|
|
s->currentUser = GetUserId();
|
|
s->prevXactReadOnly = XactReadOnly;
|
|
|
|
/*
|
|
* Initialize other subsystems for new subtransaction
|
|
*/
|
|
AtSubStart_Inval();
|
|
AtSubStart_Notify();
|
|
DeferredTriggerBeginSubXact();
|
|
|
|
s->state = TRANS_INPROGRESS;
|
|
|
|
/*
|
|
* Call start-of-subxact callbacks
|
|
*/
|
|
CallXactCallbacks(XACT_EVENT_START_SUB, s->parent->transactionIdData);
|
|
|
|
ShowTransactionState("StartSubTransaction");
|
|
}
|
|
|
|
/*
|
|
* CommitSubTransaction
|
|
*/
|
|
static void
|
|
CommitSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
ShowTransactionState("CommitSubTransaction");
|
|
|
|
if (s->state != TRANS_INPROGRESS)
|
|
elog(WARNING, "CommitSubTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
/* Pre-commit processing goes here -- nothing to do at the moment */
|
|
|
|
s->state = TRANS_COMMIT;
|
|
|
|
/* Mark subtransaction as subcommitted */
|
|
CommandCounterIncrement();
|
|
RecordSubTransactionCommit();
|
|
AtSubCommit_childXids();
|
|
|
|
/* Post-commit cleanup */
|
|
DeferredTriggerEndSubXact(true);
|
|
AtSubCommit_Portals(s->parent->transactionIdData,
|
|
s->parent->curTransactionOwner);
|
|
AtEOSubXact_LargeObject(true, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
AtSubCommit_Notify();
|
|
AtEOSubXact_UpdatePasswordFile(true, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
AtSubCommit_smgr();
|
|
|
|
CallXactCallbacks(XACT_EVENT_COMMIT_SUB, s->parent->transactionIdData);
|
|
|
|
/*
|
|
* Note that we just release the resource owner's resources and don't
|
|
* delete it. This is because locks are not actually released here.
|
|
* The owner object continues to exist as a child of its parent owner
|
|
* (namely my parent transaction's resource owner), and the locks
|
|
* effectively become that owner object's responsibility.
|
|
*/
|
|
ResourceOwnerRelease(s->curTransactionOwner,
|
|
RESOURCE_RELEASE_BEFORE_LOCKS,
|
|
true, false);
|
|
AtEOSubXact_Inval(true);
|
|
/* we can skip the LOCKS phase */
|
|
ResourceOwnerRelease(s->curTransactionOwner,
|
|
RESOURCE_RELEASE_AFTER_LOCKS,
|
|
true, false);
|
|
|
|
AtEOXact_GUC(true, true);
|
|
AtEOSubXact_SPI(true, s->transactionIdData);
|
|
AtEOSubXact_on_commit_actions(true, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
AtEOSubXact_Namespace(true, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
AtEOSubXact_Files(true, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
|
|
/*
|
|
* We need to restore the upper transaction's read-only state,
|
|
* in case the upper is read-write while the child is read-only;
|
|
* GUC will incorrectly think it should leave the child state in place.
|
|
*/
|
|
XactReadOnly = s->prevXactReadOnly;
|
|
|
|
CurrentResourceOwner = s->parent->curTransactionOwner;
|
|
CurTransactionResourceOwner = s->parent->curTransactionOwner;
|
|
s->curTransactionOwner = NULL;
|
|
|
|
AtSubCommit_Memory();
|
|
|
|
s->state = TRANS_DEFAULT;
|
|
}
|
|
|
|
/*
|
|
* AbortSubTransaction
|
|
*/
|
|
static void
|
|
AbortSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
ShowTransactionState("AbortSubTransaction");
|
|
|
|
if (s->state != TRANS_INPROGRESS)
|
|
elog(WARNING, "AbortSubTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
HOLD_INTERRUPTS();
|
|
|
|
s->state = TRANS_ABORT;
|
|
|
|
/*
|
|
* Release any LW locks we might be holding as quickly as possible.
|
|
* (Regular locks, however, must be held till we finish aborting.)
|
|
* Releasing LW locks is critical since we might try to grab them
|
|
* again while cleaning up!
|
|
*
|
|
* FIXME This may be incorrect --- Are there some locks we should keep?
|
|
* Buffer locks, for example? I don't think so but I'm not sure.
|
|
*/
|
|
LWLockReleaseAll();
|
|
|
|
AbortBufferIO();
|
|
UnlockBuffers();
|
|
|
|
LockWaitCancel();
|
|
|
|
/*
|
|
* do abort processing
|
|
*/
|
|
AtSubAbort_Memory();
|
|
|
|
DeferredTriggerEndSubXact(false);
|
|
AtSubAbort_Portals(s->parent->transactionIdData,
|
|
s->parent->curTransactionOwner);
|
|
AtEOSubXact_LargeObject(false, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
AtSubAbort_Notify();
|
|
AtEOSubXact_UpdatePasswordFile(false, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
|
|
/* Advertise the fact that we aborted in pg_clog. */
|
|
RecordSubTransactionAbort();
|
|
|
|
/* Post-abort cleanup */
|
|
AtSubAbort_smgr();
|
|
|
|
CallXactCallbacks(XACT_EVENT_ABORT_SUB, s->parent->transactionIdData);
|
|
|
|
ResourceOwnerRelease(s->curTransactionOwner,
|
|
RESOURCE_RELEASE_BEFORE_LOCKS,
|
|
false, false);
|
|
AtEOSubXact_Inval(false);
|
|
ResourceOwnerRelease(s->curTransactionOwner,
|
|
RESOURCE_RELEASE_LOCKS,
|
|
false, false);
|
|
ResourceOwnerRelease(s->curTransactionOwner,
|
|
RESOURCE_RELEASE_AFTER_LOCKS,
|
|
false, false);
|
|
|
|
AtEOXact_GUC(false, true);
|
|
AtEOSubXact_SPI(false, s->transactionIdData);
|
|
AtEOSubXact_on_commit_actions(false, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
AtEOSubXact_Namespace(false, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
AtEOSubXact_Files(false, s->transactionIdData,
|
|
s->parent->transactionIdData);
|
|
|
|
/*
|
|
* Reset user id which might have been changed transiently. Here we
|
|
* want to restore to the userid that was current at subxact entry.
|
|
* (As in AbortTransaction, we need not worry about the session userid.)
|
|
*
|
|
* Must do this after AtEOXact_GUC to handle the case where we entered
|
|
* the subxact inside a SECURITY DEFINER function (hence current and
|
|
* session userids were different) and then session auth was changed
|
|
* inside the subxact. GUC will reset both current and session userids
|
|
* to the entry-time session userid. This is right in every other
|
|
* scenario so it seems simplest to let GUC do that and fix it here.
|
|
*/
|
|
SetUserId(s->currentUser);
|
|
|
|
/*
|
|
* Restore the upper transaction's read-only state, too. This should
|
|
* be redundant with GUC's cleanup but we may as well do it for
|
|
* consistency with the commit case.
|
|
*/
|
|
XactReadOnly = s->prevXactReadOnly;
|
|
|
|
RESUME_INTERRUPTS();
|
|
}
|
|
|
|
/*
|
|
* CleanupSubTransaction
|
|
*/
|
|
static void
|
|
CleanupSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
ShowTransactionState("CleanupSubTransaction");
|
|
|
|
if (s->state != TRANS_ABORT)
|
|
elog(WARNING, "CleanupSubTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
AtSubCleanup_Portals();
|
|
|
|
CurrentResourceOwner = s->parent->curTransactionOwner;
|
|
CurTransactionResourceOwner = s->parent->curTransactionOwner;
|
|
ResourceOwnerDelete(s->curTransactionOwner);
|
|
s->curTransactionOwner = NULL;
|
|
|
|
AtSubCleanup_Memory();
|
|
|
|
s->state = TRANS_DEFAULT;
|
|
}
|
|
|
|
/*
|
|
* StartAbortedSubTransaction
|
|
*
|
|
* This function is used to start a subtransaction and put it immediately
|
|
* into aborted state. The end result should be equivalent to
|
|
* StartSubTransaction immediately followed by AbortSubTransaction.
|
|
* The reason we don't implement it just that way is that many of the backend
|
|
* modules aren't designed to handle starting a subtransaction when not
|
|
* inside a valid transaction. Rather than making them all capable of
|
|
* doing that, we just omit the paired start and abort calls in this path.
|
|
*/
|
|
static void
|
|
StartAbortedSubTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->state != TRANS_DEFAULT)
|
|
elog(WARNING, "StartAbortedSubTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
s->state = TRANS_START;
|
|
|
|
/*
|
|
* We don't bother to generate a new Xid, so the end state is not
|
|
* *exactly* like we had done a full Start/AbortSubTransaction...
|
|
*/
|
|
s->transactionIdData = InvalidTransactionId;
|
|
|
|
/* Make sure currentUser is reasonably valid */
|
|
Assert(s->parent != NULL);
|
|
s->currentUser = s->parent->currentUser;
|
|
|
|
/*
|
|
* Initialize only what has to be there for CleanupSubTransaction to work.
|
|
*/
|
|
AtSubStart_Memory();
|
|
AtSubStart_ResourceOwner();
|
|
|
|
s->state = TRANS_ABORT;
|
|
|
|
AtSubAbort_Memory();
|
|
|
|
ShowTransactionState("StartAbortedSubTransaction");
|
|
}
|
|
|
|
/*
|
|
* PushTransaction
|
|
* Set up transaction state for a subtransaction
|
|
*
|
|
* The caller has to make sure to always reassign CurrentTransactionState
|
|
* if it has a local pointer to it after calling this function.
|
|
*/
|
|
static void
|
|
PushTransaction(void)
|
|
{
|
|
TransactionState p = CurrentTransactionState;
|
|
TransactionState s;
|
|
|
|
/*
|
|
* We keep subtransaction state nodes in TopTransactionContext.
|
|
*/
|
|
s = (TransactionState)
|
|
MemoryContextAllocZero(TopTransactionContext,
|
|
sizeof(TransactionStateData));
|
|
s->parent = p;
|
|
s->nestingLevel = p->nestingLevel + 1;
|
|
s->savepointLevel = p->savepointLevel;
|
|
s->state = TRANS_DEFAULT;
|
|
s->blockState = TBLOCK_SUBBEGIN;
|
|
|
|
/* Command IDs count in a continuous sequence through subtransactions */
|
|
s->commandId = p->commandId;
|
|
|
|
/*
|
|
* Copy down some other data so that we will have valid state until
|
|
* StartSubTransaction runs.
|
|
*/
|
|
s->transactionIdData = p->transactionIdData;
|
|
s->curTransactionContext = p->curTransactionContext;
|
|
s->curTransactionOwner = p->curTransactionOwner;
|
|
s->currentUser = p->currentUser;
|
|
|
|
CurrentTransactionState = s;
|
|
}
|
|
|
|
/*
|
|
* PopTransaction
|
|
* Pop back to parent transaction state
|
|
*
|
|
* The caller has to make sure to always reassign CurrentTransactionState
|
|
* if it has a local pointer to it after calling this function.
|
|
*/
|
|
static void
|
|
PopTransaction(void)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
|
|
if (s->state != TRANS_DEFAULT)
|
|
elog(WARNING, "PopTransaction while in %s state",
|
|
TransStateAsString(s->state));
|
|
|
|
if (s->parent == NULL)
|
|
elog(FATAL, "PopTransaction with no parent");
|
|
|
|
/* Command IDs count in a continuous sequence through subtransactions */
|
|
s->parent->commandId = s->commandId;
|
|
|
|
CurrentTransactionState = s->parent;
|
|
|
|
/* Let's just make sure CurTransactionContext is good */
|
|
CurTransactionContext = s->parent->curTransactionContext;
|
|
MemoryContextSwitchTo(CurTransactionContext);
|
|
|
|
/* Ditto for ResourceOwner links */
|
|
CurTransactionResourceOwner = s->parent->curTransactionOwner;
|
|
CurrentResourceOwner = s->parent->curTransactionOwner;
|
|
|
|
/* Free the old child structure */
|
|
if (s->name)
|
|
pfree(s->name);
|
|
pfree(s);
|
|
}
|
|
|
|
/*
|
|
* ShowTransactionState
|
|
* Debug support
|
|
*/
|
|
static void
|
|
ShowTransactionState(const char *str)
|
|
{
|
|
/* skip work if message will definitely not be printed */
|
|
if (log_min_messages <= DEBUG2 || client_min_messages <= DEBUG2)
|
|
{
|
|
elog(DEBUG2, "%s", str);
|
|
ShowTransactionStateRec(CurrentTransactionState);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ShowTransactionStateRec
|
|
* Recursive subroutine for ShowTransactionState
|
|
*/
|
|
static void
|
|
ShowTransactionStateRec(TransactionState s)
|
|
{
|
|
if (s->parent)
|
|
ShowTransactionStateRec(s->parent);
|
|
|
|
/* use ereport to suppress computation if msg will not be printed */
|
|
ereport(DEBUG2,
|
|
(errmsg_internal("name: %s; blockState: %13s; state: %7s, xid/cid: %u/%02u, nestlvl: %d, children: %s",
|
|
PointerIsValid(s->name) ? s->name : "unnamed",
|
|
BlockStateAsString(s->blockState),
|
|
TransStateAsString(s->state),
|
|
(unsigned int) s->transactionIdData,
|
|
(unsigned int) s->commandId,
|
|
s->nestingLevel,
|
|
nodeToString(s->childXids))));
|
|
}
|
|
|
|
/*
|
|
* BlockStateAsString
|
|
* Debug support
|
|
*/
|
|
static const char *
|
|
BlockStateAsString(TBlockState blockState)
|
|
{
|
|
switch (blockState)
|
|
{
|
|
case TBLOCK_DEFAULT:
|
|
return "DEFAULT";
|
|
case TBLOCK_STARTED:
|
|
return "STARTED";
|
|
case TBLOCK_BEGIN:
|
|
return "BEGIN";
|
|
case TBLOCK_INPROGRESS:
|
|
return "INPROGRESS";
|
|
case TBLOCK_END:
|
|
return "END";
|
|
case TBLOCK_ABORT:
|
|
return "ABORT";
|
|
case TBLOCK_ENDABORT:
|
|
return "ENDABORT";
|
|
case TBLOCK_SUBBEGIN:
|
|
return "SUB BEGIN";
|
|
case TBLOCK_SUBINPROGRESS:
|
|
return "SUB INPROGRS";
|
|
case TBLOCK_SUBEND:
|
|
return "SUB END";
|
|
case TBLOCK_SUBABORT:
|
|
return "SUB ABORT";
|
|
case TBLOCK_SUBENDABORT_ALL:
|
|
return "SUB ENDAB ALL";
|
|
case TBLOCK_SUBENDABORT:
|
|
return "SUB ENDAB";
|
|
case TBLOCK_SUBABORT_PENDING:
|
|
return "SUB ABRT PEND";
|
|
case TBLOCK_SUBENDABORT_RELEASE:
|
|
return "SUB ENDAB REL";
|
|
}
|
|
return "UNRECOGNIZED";
|
|
}
|
|
|
|
/*
|
|
* TransStateAsString
|
|
* Debug support
|
|
*/
|
|
static const char *
|
|
TransStateAsString(TransState state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case TRANS_DEFAULT:
|
|
return "DEFAULT";
|
|
case TRANS_START:
|
|
return "START";
|
|
case TRANS_COMMIT:
|
|
return "COMMIT";
|
|
case TRANS_ABORT:
|
|
return "ABORT";
|
|
case TRANS_INPROGRESS:
|
|
return "INPROGR";
|
|
}
|
|
return "UNRECOGNIZED";
|
|
}
|
|
|
|
/*
|
|
* xactGetCommittedChildren
|
|
*
|
|
* Gets the list of committed children of the current transaction. The return
|
|
* value is the number of child transactions. *children is set to point to a
|
|
* palloc'd array of TransactionIds. If there are no subxacts, *children is
|
|
* set to NULL.
|
|
*/
|
|
int
|
|
xactGetCommittedChildren(TransactionId **ptr)
|
|
{
|
|
TransactionState s = CurrentTransactionState;
|
|
int nchildren;
|
|
TransactionId *children;
|
|
ListCell *p;
|
|
|
|
nchildren = list_length(s->childXids);
|
|
if (nchildren == 0)
|
|
{
|
|
*ptr = NULL;
|
|
return 0;
|
|
}
|
|
|
|
children = (TransactionId *) palloc(nchildren * sizeof(TransactionId));
|
|
*ptr = children;
|
|
|
|
foreach(p, s->childXids)
|
|
{
|
|
TransactionId child = lfirst_xid(p);
|
|
|
|
*children++ = child;
|
|
}
|
|
|
|
return nchildren;
|
|
}
|
|
|
|
/*
|
|
* XLOG support routines
|
|
*/
|
|
|
|
void
|
|
xact_redo(XLogRecPtr lsn, XLogRecord *record)
|
|
{
|
|
uint8 info = record->xl_info & ~XLR_INFO_MASK;
|
|
|
|
if (info == XLOG_XACT_COMMIT)
|
|
{
|
|
xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record);
|
|
int i;
|
|
|
|
TransactionIdCommit(record->xl_xid);
|
|
/* Mark committed subtransactions as committed */
|
|
TransactionIdCommitTree(xlrec->nsubxacts,
|
|
(TransactionId *) &(xlrec->xnodes[xlrec->nrels]));
|
|
/* Make sure files supposed to be dropped are dropped */
|
|
for (i = 0; i < xlrec->nrels; i++)
|
|
{
|
|
XLogCloseRelation(xlrec->xnodes[i]);
|
|
smgrdounlink(smgropen(xlrec->xnodes[i]), false, true);
|
|
}
|
|
}
|
|
else if (info == XLOG_XACT_ABORT)
|
|
{
|
|
xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record);
|
|
int i;
|
|
|
|
TransactionIdAbort(record->xl_xid);
|
|
/* mark subtransactions as aborted */
|
|
TransactionIdAbortTree(xlrec->nsubxacts,
|
|
(TransactionId *) &(xlrec->xnodes[xlrec->nrels]));
|
|
/* Make sure files supposed to be dropped are dropped */
|
|
for (i = 0; i < xlrec->nrels; i++)
|
|
{
|
|
XLogCloseRelation(xlrec->xnodes[i]);
|
|
smgrdounlink(smgropen(xlrec->xnodes[i]), false, true);
|
|
}
|
|
}
|
|
else
|
|
elog(PANIC, "xact_redo: unknown op code %u", info);
|
|
}
|
|
|
|
void
|
|
xact_undo(XLogRecPtr lsn, XLogRecord *record)
|
|
{
|
|
uint8 info = record->xl_info & ~XLR_INFO_MASK;
|
|
|
|
if (info == XLOG_XACT_COMMIT) /* shouldn't be called by XLOG */
|
|
elog(PANIC, "xact_undo: can't undo committed xaction");
|
|
else if (info != XLOG_XACT_ABORT)
|
|
elog(PANIC, "xact_redo: unknown op code %u", info);
|
|
}
|
|
|
|
void
|
|
xact_desc(char *buf, uint8 xl_info, char *rec)
|
|
{
|
|
uint8 info = xl_info & ~XLR_INFO_MASK;
|
|
int i;
|
|
|
|
if (info == XLOG_XACT_COMMIT)
|
|
{
|
|
xl_xact_commit *xlrec = (xl_xact_commit *) rec;
|
|
struct tm *tm = localtime(&xlrec->xtime);
|
|
|
|
sprintf(buf + strlen(buf), "commit: %04u-%02u-%02u %02u:%02u:%02u",
|
|
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
if (xlrec->nrels > 0)
|
|
{
|
|
sprintf(buf + strlen(buf), "; rels:");
|
|
for (i = 0; i < xlrec->nrels; i++)
|
|
{
|
|
RelFileNode rnode = xlrec->xnodes[i];
|
|
sprintf(buf + strlen(buf), " %u/%u/%u",
|
|
rnode.spcNode, rnode.dbNode, rnode.relNode);
|
|
}
|
|
}
|
|
if (xlrec->nsubxacts > 0)
|
|
{
|
|
TransactionId *xacts = (TransactionId *)
|
|
&xlrec->xnodes[xlrec->nrels];
|
|
|
|
sprintf(buf + strlen(buf), "; subxacts:");
|
|
for (i = 0; i < xlrec->nsubxacts; i++)
|
|
sprintf(buf + strlen(buf), " %u", xacts[i]);
|
|
}
|
|
}
|
|
else if (info == XLOG_XACT_ABORT)
|
|
{
|
|
xl_xact_abort *xlrec = (xl_xact_abort *) rec;
|
|
struct tm *tm = localtime(&xlrec->xtime);
|
|
|
|
sprintf(buf + strlen(buf), "abort: %04u-%02u-%02u %02u:%02u:%02u",
|
|
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
if (xlrec->nrels > 0)
|
|
{
|
|
sprintf(buf + strlen(buf), "; rels:");
|
|
for (i = 0; i < xlrec->nrels; i++)
|
|
{
|
|
RelFileNode rnode = xlrec->xnodes[i];
|
|
sprintf(buf + strlen(buf), " %u/%u/%u",
|
|
rnode.spcNode, rnode.dbNode, rnode.relNode);
|
|
}
|
|
}
|
|
if (xlrec->nsubxacts > 0)
|
|
{
|
|
TransactionId *xacts = (TransactionId *)
|
|
&xlrec->xnodes[xlrec->nrels];
|
|
|
|
sprintf(buf + strlen(buf), "; subxacts:");
|
|
for (i = 0; i < xlrec->nsubxacts; i++)
|
|
sprintf(buf + strlen(buf), " %u", xacts[i]);
|
|
}
|
|
}
|
|
else
|
|
strcat(buf, "UNKNOWN");
|
|
}
|
|
|
|
void
|
|
XactPushRollback(void (*func) (void *), void *data)
|
|
{
|
|
#ifdef XLOG_II
|
|
if (_RollbackFunc != NULL)
|
|
elog(PANIC, "XactPushRollback: already installed");
|
|
#endif
|
|
|
|
_RollbackFunc = func;
|
|
_RollbackData = data;
|
|
}
|
|
|
|
void
|
|
XactPopRollback(void)
|
|
{
|
|
_RollbackFunc = NULL;
|
|
}
|