/*-------------------------------------------------------------------------
 *
 * xact.c
 *	  top level transaction system support routines
 *
 * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql-server/src/backend/access/transam/xact.c,v 1.135.2.1 2002/11/18 01:17:50 tgl Exp $
 *
 * NOTES
 *		Transaction aborts can now occur two ways:
 *
 *		1)	system dies from some internal cause  (Assert, etc..)
 *		2)	user types abort
 *
 *		These two cases used to be treated identically, but now
 *		we need to distinguish them.  Why?	consider the following
 *		two situations:
 *
 *				case 1							case 2
 *				------							------
 *		1) user types BEGIN				1) user types BEGIN
 *		2) user does something			2) user does something
 *		3) user does not like what		3) system aborts for some reason
 *		   she sees and types ABORT
 *
 *		In case 1, we want to abort the transaction and return to the
 *		default state.	In case 2, there may be more commands coming
 *		our way which are part of the same transaction block and we have
 *		to ignore these commands until we see an END transaction.
 *		(or an ABORT! --djm)
 *
 *		Internal aborts are now handled by AbortTransactionBlock(), just as
 *		they always have been, and user aborts are now handled by
 *		UserAbortTransactionBlock().  Both of them rely on AbortTransaction()
 *		to do all the real work.  The only difference is what state we
 *		enter after AbortTransaction() does its work:
 *
 *		* AbortTransactionBlock() leaves us in TBLOCK_ABORT and
 *		* UserAbortTransactionBlock() leaves us in TBLOCK_ENDABORT
 *
 *		Low-level transaction abort handling is divided into two phases:
 *		* AbortTransaction() executes as soon as we realize the transaction
 *		  has failed.  It should release all shared resources (locks etc)
 *		  so that we do not delay other backends unnecessarily.
 *		* CleanupTransaction() executes when we finally see a user COMMIT
 *		  or ROLLBACK command; it cleans things up and gets us out of
 *		  the transaction internally.  In particular, we mustn't destroy
 *		  TransactionCommandContext until this point.
 *
 *	 NOTES
 *		This file is an attempt at a redesign of the upper layer
 *		of the V1 transaction system which was too poorly thought
 *		out to describe.  This new system hopes to be both simpler
 *		in design, simpler to extend and needs to contain added
 *		functionality to solve problems beyond the scope of the V1
 *		system.  (In particuler, communication of transaction
 *		information between parallel backends has to be supported)
 *
 *		The essential aspects of the transaction system are:
 *
 *				o  transaction id generation
 *				o  transaction log updating
 *				o  memory cleanup
 *				o  cache invalidation
 *				o  lock cleanup
 *
 *		Hence, the functional division of the transaction code is
 *		based on what of the above things need to be done during
 *		a start/commit/abort transaction.  For instance, the
 *		routine AtCommit_Memory() takes care of all the memory
 *		cleanup stuff done at commit time.
 *
 *		The code is layered as follows:
 *
 *				StartTransaction
 *				CommitTransaction
 *				AbortTransaction
 *				CleanupTransaction
 *
 *		are provided to do the lower level work like recording
 *		the transaction status in the log and doing memory cleanup.
 *		above these routines are another set of functions:
 *
 *				StartTransactionCommand
 *				CommitTransactionCommand
 *				AbortCurrentTransaction
 *
 *		These are the routines used in the postgres main processing
 *		loop.  They are sensitive to the current transaction block state
 *		and make calls to the lower level routines appropriately.
 *
 *		Support for transaction blocks is provided via the functions:
 *
 *				StartTransactionBlock
 *				CommitTransactionBlock
 *				AbortTransactionBlock
 *
 *		These are invoked only in responce to a user "BEGIN", "END",
 *		or "ABORT" command.  The tricky part about these functions
 *		is that they are called within the postgres main loop, in between
 *		the StartTransactionCommand() and CommitTransactionCommand().
 *
 *		For example, consider the following sequence of user commands:
 *
 *		1)		begin
 *		2)		retrieve (foo.all)
 *		3)		append foo (bar = baz)
 *		4)		end
 *
 *		in the main processing loop, this results in the following
 *		transaction sequence:
 *
 *			/	StartTransactionCommand();
 *		1) /	ProcessUtility();				<< begin
 *		   \		StartTransactionBlock();
 *			\	CommitTransactionCommand();
 *
 *			/	StartTransactionCommand();
 *		2) <	ProcessQuery();					<< retrieve (foo.all)
 *			\	CommitTransactionCommand();
 *
 *			/	StartTransactionCommand();
 *		3) <	ProcessQuery();					<< append foo (bar = baz)
 *			\	CommitTransactionCommand();
 *
 *			/	StartTransactionCommand();
 *		4) /	ProcessUtility();				<< end
 *		   \		CommitTransactionBlock();
 *			\	CommitTransactionCommand();
 *
 *		The point of this example is to demonstrate the need for
 *		StartTransactionCommand() and CommitTransactionCommand() to
 *		be state smart -- they should do nothing in between the calls
 *		to StartTransactionBlock() and EndTransactionBlock() and
 *		outside these calls they need to do normal start/commit
 *		processing.
 *
 *		Furthermore, suppose the "retrieve (foo.all)" caused an abort
 *		condition.	We would then want to abort the transaction and
 *		ignore all subsequent commands up to the "end".
 *		-cim 3/23/90
 *
 *-------------------------------------------------------------------------
 */

/*
 * Large object clean up added in CommitTransaction() to prevent buffer leaks.
 * [PA, 7/17/98]
 * [PA] is Pascal André <andre@via.ecp.fr>
 */
#include "postgres.h"

#include <unistd.h>
#include <sys/time.h>

#include "access/gistscan.h"
#include "access/hash.h"
#include "access/nbtree.h"
#include "access/rtree.h"
#include "access/xact.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "commands/async.h"
#include "commands/trigger.h"
#include "commands/user.h"
#include "executor/spi.h"
#include "libpq/be-fsstubs.h"
#include "miscadmin.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/catcache.h"
#include "utils/relcache.h"
#include "pgstat.h"


static void AbortTransaction(void);
static void AtAbort_Cache(void);
static void AtAbort_Locks(void);
static void AtAbort_Memory(void);
static void AtCleanup_Memory(void);
static void AtCommit_Cache(void);
static void AtCommit_LocalCache(void);
static void AtCommit_Locks(void);
static void AtCommit_Memory(void);
static void AtStart_Cache(void);
static void AtStart_Locks(void);
static void AtStart_Memory(void);
static void CleanupTransaction(void);
static void CommitTransaction(void);
static void RecordTransactionAbort(void);
static void StartTransaction(void);

/* ----------------
 *		global variables holding the current transaction state.
 * ----------------
 */
static TransactionStateData CurrentTransactionStateData = {
	0,							/* transaction id */
	FirstCommandId,				/* command id */
	0,							/* scan command id */
	0x0,						/* start time */
	TRANS_DEFAULT,				/* transaction state */
	TBLOCK_DEFAULT				/* transaction block state */
};

TransactionState CurrentTransactionState = &CurrentTransactionStateData;

/*
 * User-tweakable parameters
 */
int			DefaultXactIsoLevel = XACT_READ_COMMITTED;
int			XactIsoLevel;

bool		autocommit = true;

int			CommitDelay = 0;	/* precommit delay in microseconds */
int			CommitSiblings = 5; /* number of concurrent xacts needed to
								 * sleep */


static bool suppressChain = false;

static void (*_RollbackFunc) (void *) = NULL;
static void *_RollbackData = NULL;


/* ----------------------------------------------------------------
 *					 transaction state accessors
 * ----------------------------------------------------------------
 */

#ifdef NOT_USED

/* --------------------------------
 *		TransactionFlushEnabled()
 *		SetTransactionFlushEnabled()
 *
 *		These are used to test and set the "TransactionFlushState"
 *		varable.  If this variable is true (the default), then
 *		the system will flush all dirty buffers to disk at the end
 *		of each transaction.   If false then we are assuming the
 *		buffer pool resides in stable main memory, in which case we
 *		only do writes as necessary.
 * --------------------------------
 */
static int	TransactionFlushState = 1;

int
TransactionFlushEnabled(void)
{
	return TransactionFlushState;
}

void
SetTransactionFlushEnabled(bool state)
{
	TransactionFlushState = (state == true);
}
#endif


/* --------------------------------
 *		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)
		return true;

	return false;
}


/* --------------------------------
 *		GetCurrentTransactionId
 * --------------------------------
 */
TransactionId
GetCurrentTransactionId(void)
{
	TransactionState s = CurrentTransactionState;

	return s->transactionIdData;
}


/* --------------------------------
 *		GetCurrentCommandId
 * --------------------------------
 */
CommandId
GetCurrentCommandId(void)
{
	TransactionState s = CurrentTransactionState;

	return s->commandId;
}


/* --------------------------------
 *		GetCurrentTransactionStartTime
 * --------------------------------
 */
AbsoluteTime
GetCurrentTransactionStartTime(void)
{
	TransactionState s = CurrentTransactionState;

	return s->startTime;
}


/* --------------------------------
 *		GetCurrentTransactionStartTimeUsec
 * --------------------------------
 */
AbsoluteTime
GetCurrentTransactionStartTimeUsec(int *msec)
{
	TransactionState s = CurrentTransactionState;

	*msec = s->startTimeUsec;

	return s->startTime;
}


/* --------------------------------
 *		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;
	}

	return TransactionIdEquals(xid, s->transactionIdData);
}


/* --------------------------------
 *		CommandIdIsCurrentCommandId
 * --------------------------------
 */
bool
CommandIdIsCurrentCommandId(CommandId cid)
{
	TransactionState s = CurrentTransactionState;

	return (cid == s->commandId) ? true : false;
}


/* --------------------------------
 *		CommandCounterIncrement
 * --------------------------------
 */
void
CommandCounterIncrement(void)
{
	TransactionState s = CurrentTransactionState;

	s->commandId += 1;
	if (s->commandId == FirstCommandId) /* check for overflow */
		elog(ERROR, "You may only have 2^32-1 commands per 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() instead of
	 * AtCommit_Cache() is called here.
	 */
	AtCommit_LocalCache();
	AtStart_Cache();
}


/* ----------------------------------------------------------------
 *						StartTransaction stuff
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *		AtStart_Cache
 * --------------------------------
 */
static void
AtStart_Cache(void)
{
	AcceptInvalidationMessages();
}

/* --------------------------------
 *		AtStart_Locks
 * --------------------------------
 */
static void
AtStart_Locks(void)
{
	/*
	 * at present, it is unknown to me what belongs here -cim 3/18/90
	 *
	 * There isn't anything to do at the start of a xact for locks. -mer
	 * 5/24/92
	 */
}

/* --------------------------------
 *		AtStart_Memory
 * --------------------------------
 */
static void
AtStart_Memory(void)
{
	/*
	 * We shouldn't have any transaction contexts already.
	 */
	Assert(TopTransactionContext == NULL);
	Assert(TransactionCommandContext == NULL);

	/*
	 * Create a toplevel context for the transaction.
	 */
	TopTransactionContext =
		AllocSetContextCreate(TopMemoryContext,
							  "TopTransactionContext",
							  ALLOCSET_DEFAULT_MINSIZE,
							  ALLOCSET_DEFAULT_INITSIZE,
							  ALLOCSET_DEFAULT_MAXSIZE);

	/*
	 * Create a statement-level context and make it active.
	 */
	TransactionCommandContext =
		AllocSetContextCreate(TopTransactionContext,
							  "TransactionCommandContext",
							  ALLOCSET_DEFAULT_MINSIZE,
							  ALLOCSET_DEFAULT_INITSIZE,
							  ALLOCSET_DEFAULT_MAXSIZE);
	MemoryContextSwitchTo(TransactionCommandContext);
}


/* ----------------------------------------------------------------
 *						CommitTransaction stuff
 * ----------------------------------------------------------------
 */

/*
 *		RecordTransactionCommit
 */
void
RecordTransactionCommit(void)
{
	/*
	 * If we made neither any XLOG entries nor any temp-rel updates, we
	 * can omit recording the transaction commit at all.
	 */
	if (MyXactMadeXLogEntry || MyXactMadeTempRelUpdate)
	{
		TransactionId xid = GetCurrentTransactionId();
		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.  (Otherwise, its XID
		 * appears nowhere in permanent storage, so no one else will ever
		 * care if it committed.)
		 */
		if (MyLastRecPtr.xrecoff != 0)
		{
			/* Need to emit a commit record */
			XLogRecData rdata;
			xl_xact_commit xlrec;

			xlrec.xtime = time(NULL);
			rdata.buffer = InvalidBuffer;
			rdata.data = (char *) (&xlrec);
			rdata.len = SizeOfXactCommit;
			rdata.next = NULL;

			/*
			 * XXX SHOULD SAVE ARRAY OF RELFILENODE-s TO DROP
			 */
			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.
		 */
		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)
			{
				struct timeval delay;

				delay.tv_sec = 0;
				delay.tv_usec = CommitDelay;
				(void) select(0, NULL, NULL, NULL, &delay);
			}

			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 (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate)
			TransactionIdCommit(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;
}


/* --------------------------------
 *		AtCommit_Cache
 * --------------------------------
 */
static void
AtCommit_Cache(void)
{
	/*
	 * Clean up the relation cache.
	 */
	AtEOXact_RelationCache(true);

	/*
	 * Make catalog changes visible to all backends.
	 */
	AtEOXactInvalidationMessages(true);
}

/* --------------------------------
 *		AtCommit_LocalCache
 * --------------------------------
 */
static void
AtCommit_LocalCache(void)
{
	/*
	 * Make catalog changes visible to me for the next command.
	 */
	CommandEndInvalidationMessages(true);
}

/* --------------------------------
 *		AtCommit_Locks
 * --------------------------------
 */
static void
AtCommit_Locks(void)
{
	/*
	 * XXX What if ProcReleaseLocks fails?	(race condition?)
	 *
	 * Then you're up a creek! -mer 5/24/92
	 */
	ProcReleaseLocks(true);
}

/* --------------------------------
 *		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;
	TransactionCommandContext = NULL;
}

/* ----------------------------------------------------------------
 *						AbortTransaction stuff
 * ----------------------------------------------------------------
 */

/*
 *		RecordTransactionAbort
 */
static void
RecordTransactionAbort(void)
{
	/*
	 * If we made neither any transaction-controlled XLOG entries nor any
	 * temp-rel updates, we can omit recording the transaction abort at
	 * all. No one will ever care that it aborted.
	 */
	if (MyLastRecPtr.xrecoff != 0 || MyXactMadeTempRelUpdate)
	{
		TransactionId xid = GetCurrentTransactionId();

		/*
		 * Catch the scenario where we aborted partway through
		 * RecordTransactionCommit ...
		 */
		if (TransactionIdDidCommit(xid))
			elog(PANIC, "RecordTransactionAbort: xact %u already committed",
				 xid);

		START_CRIT_SECTION();

		/*
		 * We only need to log the abort in XLOG if the transaction made
		 * any transaction-controlled XLOG entries.  (Otherwise, its XID
		 * appears nowhere in permanent storage, so no one else will ever
		 * care if it committed.)  We do not flush XLOG to disk in any
		 * case, since the default assumption after a crash would be that
		 * we aborted, anyway.
		 */
		if (MyLastRecPtr.xrecoff != 0)
		{
			XLogRecData rdata;
			xl_xact_abort xlrec;
			XLogRecPtr	recptr;

			xlrec.xtime = time(NULL);
			rdata.buffer = InvalidBuffer;
			rdata.data = (char *) (&xlrec);
			rdata.len = SizeOfXactAbort;
			rdata.next = NULL;

			/*
			 * SHOULD SAVE ARRAY OF RELFILENODE-s TO DROP
			 */
			recptr = XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, &rdata);
		}

		/*
		 * Mark the transaction aborted in clog.  This is not absolutely
		 * necessary but we may as well do it while we are here.
		 */
		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;
}

/* --------------------------------
 *		AtAbort_Cache
 * --------------------------------
 */
static void
AtAbort_Cache(void)
{
	AtEOXact_RelationCache(false);
	AtEOXactInvalidationMessages(false);
}

/* --------------------------------
 *		AtAbort_Locks
 * --------------------------------
 */
static void
AtAbort_Locks(void)
{
	/*
	 * XXX What if ProcReleaseLocks() fails?  (race condition?)
	 *
	 * Then you're up a creek without a paddle! -mer
	 */
	ProcReleaseLocks(false);
}


/* --------------------------------
 *		AtAbort_Memory
 * --------------------------------
 */
static void
AtAbort_Memory(void)
{
	/*
	 * Make sure we are in a valid context (not a child of
	 * TransactionCommandContext...).  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 (TransactionCommandContext != NULL)
	{
		MemoryContextSwitchTo(TransactionCommandContext);

		/*
		 * We do not want to destroy transaction contexts yet, but it
		 * should be OK to delete any command-local memory.
		 */
		MemoryContextResetAndDeleteChildren(TransactionCommandContext);
	}
	else
		MemoryContextSwitchTo(TopMemoryContext);
}


/* ----------------------------------------------------------------
 *						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);

	/*
	 * Release all transaction-local memory.
	 */
	if (TopTransactionContext != NULL)
		MemoryContextDelete(TopTransactionContext);
	TopTransactionContext = NULL;
	TransactionCommandContext = NULL;
}


/* ----------------------------------------------------------------
 *						interface routines
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *		StartTransaction
 *
 * --------------------------------
 */
static void
StartTransaction(void)
{
	TransactionState s = CurrentTransactionState;

	FreeXactSnapshot();
	XactIsoLevel = DefaultXactIsoLevel;

	/*
	 * Check the current transaction state.  If the transaction system is
	 * switched off, or if we're already in a transaction, do nothing.
	 * We're already in a transaction when the monitor sends a null
	 * command to the backend to flush the comm channel.  This is a hacky
	 * fix to a communications problem, and we keep having to deal with it
	 * here.  We should fix the comm channel code.	mao 080891
	 */
	if (s->state == TRANS_INPROGRESS)
		return;

	/*
	 * set the current transaction state information appropriately during
	 * start processing
	 */
	s->state = TRANS_START;

	SetReindexProcessing(false);

	/*
	 * generate a new transaction id
	 */
	s->transactionIdData = GetNewTransactionId();

	XactLockTableInsert(s->transactionIdData);

	/*
	 * initialize current transaction state fields
	 */
	s->commandId = FirstCommandId;
	s->startTime = GetCurrentAbsoluteTimeUsec(&(s->startTimeUsec));

	/*
	 * initialize the various transaction subsystems
	 */
	AtStart_Memory();
	AtStart_Cache();
	AtStart_Locks();

	/*
	 * Tell the trigger manager to we're starting a transaction
	 */
	DeferredTriggerBeginXact();

	/*
	 * done with start processing, set current transaction state to "in
	 * progress"
	 */
	s->state = TRANS_INPROGRESS;

}

#ifdef NOT_USED
/* ---------------
 * Tell me if we are currently in progress
 * ---------------
 */
bool
CurrentXactInProgress(void)
{
	return CurrentTransactionState->state == TRANS_INPROGRESS;
}
#endif

/* --------------------------------
 *		CommitTransaction
 *
 * --------------------------------
 */
static void
CommitTransaction(void)
{
	TransactionState s = CurrentTransactionState;

	/*
	 * check the current transaction state
	 */
	if (s->state != TRANS_INPROGRESS)
		elog(WARNING, "CommitTransaction and not in in-progress state");

	/*
	 * 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();

	/* 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...)
	 */

	AtEOXact_portals();

	/* handle commit for large objects [ PA, 7/17/98 ] */
	/* XXX probably this does not belong here */
	lo_commit(true);

	/* NOTIFY commit must come before lower-level cleanup */
	AtCommit_Notify();

	/* Update the flat password file if we changed pg_shadow or pg_group */
	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 != (PGPROC *) NULL)
	{
		/* Lock SInvalLock because that's what GetSnapshotData uses. */
		LWLockAcquire(SInvalLock, LW_EXCLUSIVE);
		MyProc->xid = InvalidTransactionId;
		MyProc->xmin = InvalidTransactionId;
		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.
	 */

	smgrDoPendingDeletes(true);
	AtCommit_Cache();
	AtEOXact_Buffers(true);
	/* smgrcommit already done */

	AtCommit_Locks();

	AtEOXact_GUC(true);
	AtEOXact_SPI();
	AtEOXact_gist();
	AtEOXact_hash();
	AtEOXact_nbtree();
	AtEOXact_rtree();
	AtEOXact_Namespace(true);
	AtEOXact_CatCache(true);
	AtEOXact_Files();
	pgstat_count_xact_commit();
	AtCommit_Memory();

	/*
	 * 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 and not in in-progress state");

	/*
	 * 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
	 */
	SetUserId(GetSessionUserId());

	/*
	 * do abort processing
	 */
	DeferredTriggerAbortXact();
	AtEOXact_portals();
	lo_commit(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 != (PGPROC *) NULL)
	{
		/* Lock SInvalLock because that's what GetSnapshotData uses. */
		LWLockAcquire(SInvalLock, LW_EXCLUSIVE);
		MyProc->xid = InvalidTransactionId;
		MyProc->xmin = InvalidTransactionId;
		LWLockRelease(SInvalLock);
	}

	/*
	 * Post-abort cleanup.  See notes in CommitTransaction() concerning
	 * ordering.
	 */

	smgrDoPendingDeletes(false);
	AtAbort_Cache();
	AtEOXact_Buffers(false);
	smgrabort();

	AtAbort_Locks();

	AtEOXact_GUC(false);
	AtEOXact_SPI();
	AtEOXact_gist();
	AtEOXact_hash();
	AtEOXact_nbtree();
	AtEOXact_rtree();
	AtEOXact_Namespace(false);
	AtEOXact_CatCache(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 and not in abort state");

	/*
	 * do abort cleanup processing
	 */
	AtCleanup_Memory();

	/*
	 * done with abort processing, set current transaction state back to
	 * default
	 */
	s->state = TRANS_DEFAULT;
}

/* --------------------------------
 *		StartTransactionCommand
 *
 * preventChain, if true, forces autocommit behavior at the next
 * CommitTransactionCommand call.
 * --------------------------------
 */
void
StartTransactionCommand(bool preventChain)
{
	TransactionState s = CurrentTransactionState;

	/*
	 * Remember if caller wants to prevent autocommit-off chaining. This
	 * is only allowed if not already in a transaction block.
	 */
	suppressChain = preventChain;
	if (preventChain && s->blockState != TBLOCK_DEFAULT)
		elog(ERROR, "StartTransactionCommand: can't prevent chain");

	switch (s->blockState)
	{
			/*
			 * if we aren't in a transaction block, we just do our usual
			 * start transaction.
			 */
		case TBLOCK_DEFAULT:
			StartTransaction();
			break;

			/*
			 * We should never experience this -- if we do it means the
			 * BEGIN state was not changed in the previous
			 * CommitTransactionCommand().	If we get it, we print a
			 * warning and change to the in-progress state.
			 */
		case TBLOCK_BEGIN:
			elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_BEGIN");
			s->blockState = TBLOCK_INPROGRESS;
			break;

			/*
			 * This is the case when 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:
			break;

			/*
			 * As with BEGIN, we should never experience this if we do it
			 * means the END state was not changed in the previous
			 * CommitTransactionCommand().	If we get it, we print a
			 * warning, commit the transaction, start a new transaction
			 * and change to the default state.
			 */
		case TBLOCK_END:
			elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_END");
			s->blockState = TBLOCK_DEFAULT;
			CommitTransaction();
			StartTransaction();
			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;

			/*
			 * This means we somehow aborted and the last call to
			 * CommitTransactionCommand() didn't clear the state so we
			 * remain in the ENDABORT state and maybe next time we get to
			 * CommitTransactionCommand() the state will get reset to
			 * default.
			 */
		case TBLOCK_ENDABORT:
			elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_ENDABORT");
			break;
	}

	/*
	 * We must switch to TransactionCommandContext before returning. This
	 * is already done if we called StartTransaction, otherwise not.
	 */
	Assert(TransactionCommandContext != NULL);
	MemoryContextSwitchTo(TransactionCommandContext);
}

/* --------------------------------
 *		CommitTransactionCommand
 *
 * forceCommit = true forces autocommit behavior even when autocommit is off.
 * --------------------------------
 */
void
CommitTransactionCommand(bool forceCommit)
{
	TransactionState s = CurrentTransactionState;

	switch (s->blockState)
	{
			/*
			 * If we aren't in a transaction block, and we are doing
			 * autocommit, just do our usual transaction commit.  But if
			 * we aren't doing autocommit, start a transaction block
			 * automatically by switching to INPROGRESS state.	(We handle
			 * this choice here, and not earlier, so that an explicit
			 * BEGIN issued in autocommit-off mode won't issue strange
			 * warnings.)
			 *
			 * Autocommit mode is forced by either a true forceCommit
			 * parameter to me, or a true preventChain parameter to the
			 * preceding StartTransactionCommand call, or a
			 * PreventTransactionChain call during the transaction.
			 * (The parameters could be omitted, but it turns out most
			 * callers of StartTransactionCommand/CommitTransactionCommand
			 * want to force autocommit, so making them all call
			 * PreventTransactionChain would just be extra notation.)
			 */
		case TBLOCK_DEFAULT:
			if (autocommit || forceCommit || suppressChain)
				CommitTransaction();
			else
			{
				BeginTransactionBlock();
				Assert(s->blockState == TBLOCK_INPROGRESS);
				/* This code must match the TBLOCK_INPROGRESS case below: */
				CommandCounterIncrement();
				MemoryContextResetAndDeleteChildren(TransactionCommandContext);
			}
			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.	Someday we may free resources
			 * local to the command.
			 *
			 * That someday is today, at least for memory allocated in
			 * TransactionCommandContext. - vadim 03/25/97
			 */
		case TBLOCK_INPROGRESS:
			CommandCounterIncrement();
			MemoryContextResetAndDeleteChildren(TransactionCommandContext);
			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:
			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;
	}
}

/* --------------------------------
 *		AbortCurrentTransaction
 * --------------------------------
 */
void
AbortCurrentTransaction(void)
{
	TransactionState s = CurrentTransactionState;

	switch (s->blockState)
	{
			/*
			 * if we aren't in a transaction block, we just do the basic
			 * abort & cleanup transaction.
			 */
		case TBLOCK_DEFAULT:
			AbortTransaction();
			CleanupTransaction();
			break;

			/*
			 * If we are in the 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:
			s->blockState = TBLOCK_ABORT;
			AbortTransaction();
			/* CleanupTransaction happens when we exit TBLOCK_ABORT */
			break;

			/*
			 * This is the case when are somewhere in a transaction block
			 * which aborted so we abort the transaction and set the ABORT
			 * state.  Eventually an "END TRANSACTION" will fix things and
			 * restore us to a normal state.
			 */
		case TBLOCK_INPROGRESS:
			s->blockState = TBLOCK_ABORT;
			AbortTransaction();
			/* CleanupTransaction happens when we exit TBLOCK_ABORT */
			break;

			/*
			 * Here, the system was fouled up just after the user wanted
			 * to end the transaction block so we abort the transaction
			 * and put us back into the default state.
			 */
		case TBLOCK_END:
			s->blockState = TBLOCK_DEFAULT;
			AbortTransaction();
			CleanupTransaction();
			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:
			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;
	}
}

/* --------------------------------
 *		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).  In autocommit-off mode, we allow the statement if a block is
 * not already started, and force the statement to be autocommitted despite
 * the mode.
 *
 * 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())
	{
		/* translator: %s represents an SQL statement name */
		elog(ERROR, "%s cannot run inside a transaction block", 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))
	{
		/* translator: %s represents an SQL statement name */
		elog(ERROR, "%s cannot be executed from a function", stmtType);
	}
	/* If we got past IsTransactionBlock test, should be in default state */
	if (CurrentTransactionState->blockState != TBLOCK_DEFAULT)
		elog(ERROR, "PreventTransactionChain: can't prevent chain");
	/* okay to set the flag */
	suppressChain = true;
	/* If we're in autocommit-off node, generate a notice */
	if (!autocommit)
	{
		/* translator: %s represents an SQL statement name */
		elog(NOTICE, "%s will be committed automatically", stmtType);
	}
}

/* --------------------------------
 *	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.  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;
	/*
	 * 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;
	/*
	 * If we are in autocommit-off mode then it's okay, because this
	 * statement will itself start a transaction block.
	 */
	if (!autocommit && !suppressChain)
		return;
	/* translator: %s represents an SQL statement name */
	elog(ERROR, "%s may only be used in begin/end transaction blocks",
		 stmtType);
}


/* ----------------------------------------------------------------
 *					   transaction block support
 * ----------------------------------------------------------------
 */
/* --------------------------------
 *		BeginTransactionBlock
 * --------------------------------
 */
void
BeginTransactionBlock(void)
{
	TransactionState s = CurrentTransactionState;

	/*
	 * check the current transaction state
	 */
	if (s->blockState != TBLOCK_DEFAULT)
		elog(WARNING, "BEGIN: already a transaction in progress");

	/*
	 * set the current transaction block state information appropriately
	 * during begin processing
	 */
	s->blockState = TBLOCK_BEGIN;

	/*
	 * do begin processing.  NOTE: if you put anything here, check that it
	 * behaves properly in both autocommit-on and autocommit-off modes. In
	 * the latter case we will already have done some work in the new
	 * transaction.
	 */

	/*
	 * done with begin processing, set block state to inprogress
	 */
	s->blockState = TBLOCK_INPROGRESS;
}

/* --------------------------------
 *		EndTransactionBlock
 * --------------------------------
 */
void
EndTransactionBlock(void)
{
	TransactionState s = CurrentTransactionState;

	/*
	 * check the current transaction state
	 */
	if (s->blockState == TBLOCK_INPROGRESS)
	{
		/*
		 * here 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
		 */
		s->blockState = TBLOCK_END;
		return;
	}

	if (s->blockState == TBLOCK_ABORT)
	{
		/*
		 * here, we are in a transaction block which aborted and since the
		 * AbortTransaction() was already done, we do whatever is needed
		 * and change to the special "END ABORT" state.  The upcoming
		 * CommitTransactionCommand() will recognise this and then put us
		 * back in the default state.
		 */
		s->blockState = TBLOCK_ENDABORT;
		return;
	}

	/*
	 * 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.
	 */
	elog(WARNING, "COMMIT: no transaction in progress");
	AbortTransaction();
	s->blockState = TBLOCK_ENDABORT;
}

/* --------------------------------
 *		AbortTransactionBlock
 * --------------------------------
 */
#ifdef NOT_USED
static void
AbortTransactionBlock(void)
{
	TransactionState s = CurrentTransactionState;

	/*
	 * check the current transaction state
	 */
	if (s->blockState == TBLOCK_INPROGRESS)
	{
		/*
		 * here we were inside a transaction block something screwed up
		 * inside the system so we enter the abort state, do the abort
		 * processing and then return. We remain in the abort state until
		 * we see an END TRANSACTION command.
		 */
		s->blockState = TBLOCK_ABORT;
		AbortTransaction();
		return;
	}

	/*
	 * here, 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.
	 */
	elog(WARNING, "ROLLBACK: no transaction in progress");
	AbortTransaction();
	s->blockState = TBLOCK_ENDABORT;
}
#endif

/* --------------------------------
 *		UserAbortTransactionBlock
 * --------------------------------
 */
void
UserAbortTransactionBlock(void)
{
	TransactionState s = CurrentTransactionState;

	/*
	 * if the transaction has already been automatically aborted with an
	 * error, and the user subsequently types 'abort', allow it.  (the
	 * behavior is the same as if they had typed 'end'.)
	 */
	if (s->blockState == TBLOCK_ABORT)
	{
		s->blockState = TBLOCK_ENDABORT;
		return;
	}

	if (s->blockState == TBLOCK_INPROGRESS)
	{
		/*
		 * here we were inside a transaction block and we got an abort
		 * command from the user, so we move to the abort state, do the
		 * abort processing and then change to the ENDABORT state so we
		 * will end up in the default state after the upcoming
		 * CommitTransactionCommand().
		 */
		s->blockState = TBLOCK_ABORT;
		AbortTransaction();
		s->blockState = TBLOCK_ENDABORT;
		return;
	}

	/*
	 * here, 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.
	 */
	elog(WARNING, "ROLLBACK: no transaction in progress");
	AbortTransaction();
	s->blockState = TBLOCK_ENDABORT;
}

/* --------------------------------
 *		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 low-level transaction
	 */
	switch (s->state)
	{
		case TRANS_START:
		case TRANS_INPROGRESS:
		case TRANS_COMMIT:
			/* In a transaction, so clean up */
			AbortTransaction();
			CleanupTransaction();
			break;
		case TRANS_ABORT:
			/* AbortTransaction already done, still need Cleanup */
			CleanupTransaction();
			break;
		case TRANS_DEFAULT:
			/* Not in a transaction, do nothing */
			break;
	}

	/*
	 * Now reset the high-level state
	 */
	s->blockState = TBLOCK_DEFAULT;
}

bool
IsTransactionBlock(void)
{
	TransactionState s = CurrentTransactionState;

	if (s->blockState == TBLOCK_INPROGRESS
		|| s->blockState == TBLOCK_ABORT
		|| s->blockState == TBLOCK_ENDABORT)
		return true;

	return false;
}

void
xact_redo(XLogRecPtr lsn, XLogRecord *record)
{
	uint8		info = record->xl_info & ~XLR_INFO_MASK;

	if (info == XLOG_XACT_COMMIT)
	{
		TransactionIdCommit(record->xl_xid);
		/* SHOULD REMOVE FILES OF ALL DROPPED RELATIONS */
	}
	else if (info == XLOG_XACT_ABORT)
	{
		TransactionIdAbort(record->xl_xid);
		/* SHOULD REMOVE FILES OF ALL FAILED-TO-BE-CREATED RELATIONS */
	}
	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;

	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);
	}
	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);
	}
	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;
}
