/* ----------------------------------------------------------------
 *   FILE
 *	transam.c
 *	
 *   DESCRIPTION
 *	postgres transaction log/time interface routines
 *
 *   INTERFACE ROUTINES
 *	RecoveryCheckingEnabled
 *	SetRecoveryCheckingEnabled
 *	InitializeTransactionLog
 *	TransactionIdDidCommit
 *	TransactionIdDidAbort
 *	TransactionIdIsInProgress
 *	TransactionIdCommit
 *	TransactionIdAbort
 *	TransactionIdSetInProgress
 *   	
 *   NOTES
 *	This file contains the high level access-method
 *	interface to the transaction system.
 *
 *   IDENTIFICATION
 *	$Header: /usr/local/dev/postgres/mastertree/newconf/RCS/transam.c,v 1.12 1992/05/28 20:20:52 mer Exp $
 * ----------------------------------------------------------------
 */

#include "tmp/postgres.h"

 RcsId("$Header: /usr/local/dev/postgres/mastertree/newconf/RCS/transam.c,v 1.12 1992/05/28 20:20:52 mer Exp $");

#include "machine.h"		/* in port/ directory (needed for BLCKSZ) */

#include "access/heapam.h"
#include "storage/buf.h"
#include "storage/bufmgr.h"

#include "utils/memutils.h"
#include "utils/mcxt.h"
#include "utils/rel.h"
#include "utils/log.h"

#include "utils/nabstime.h"
#include "catalog/catname.h"

#include "access/transam.h"
#include "access/xact.h"

/* ----------------
 *    global variables holding pointers to relations used
 *    by the transaction system.  These are initialized by
 *    InitializeTransactionLog().
 * ----------------
 */

Relation LogRelation	  = (Relation) NULL;
Relation TimeRelation	  = (Relation) NULL;
Relation VariableRelation = (Relation) NULL;

/* ----------------
 *    	global variables holding cached transaction id's and statuses.
 * ----------------
 */
TransactionId	cachedGetCommitTimeXid;
Time 		cachedGetCommitTime;
TransactionId	cachedTestXid;
XidStatus	cachedTestXidStatus;

/* ----------------
 *	transaction system constants
 * ----------------
 */
TransactionId NullTransactionId = (TransactionId) 0;

TransactionId AmiTransactionId = (TransactionId) 512;

TransactionId FirstTransactionId = (TransactionId) 514;

/* ----------------
 *	transaction recovery state variables
 *
 *	When the transaction system is initialized, we may
 *	need to do recovery checking.  This decision is decided
 *	by the postmaster or the user by supplying the backend
 *	with a special flag.  In general, we want to do recovery
 *	checking whenever we are running without a postmaster
 *	or when the number of backends running under the postmaster
 *	goes from zero to one. -cim 3/21/90
 * ----------------
 */
int RecoveryCheckingEnableState = 0;

/* ------------------
 *	spinlock for oid generation
 * -----------------
 */
extern int OidGenLockId;
/* ----------------
 *	recovery checking accessors
 * ----------------
 */
int
RecoveryCheckingEnabled()
{    
    return RecoveryCheckingEnableState;
}

void
SetRecoveryCheckingEnabled(state)
    bool state;
{    
    RecoveryCheckingEnableState = (state == true);
}

/* ----------------------------------------------------------------
 *	postgres log/time access method interface
 *
 *	TransactionLogTest
 *	TransactionLogUpdate
 *	========
 *	   these functions do work for the interface
 *	   functions - they search/retrieve and append/update
 *	   information in the log and time relations.
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *	TransactionLogTest
 * --------------------------------
 */

bool	/* true/false: does transaction id have specified status? */
TransactionLogTest(transactionId, status)
    TransactionId 	transactionId; 	/* transaction id to test */
    XidStatus 		status;		/* transaction status */
{
    BlockNumber		blockNumber;
    XidStatus		xidstatus;	/* recorded status of xid */
    bool		fail = false;      	/* success/failure */
    
    /* ----------------
     * 	during initialization consider all transactions
     *  as having been committed
     * ----------------
     */
    if (! RelationIsValid(LogRelation))
	return (bool) (status == XID_COMMIT);

    /* ----------------
     *	 before going to the buffer manager, check our single
     *   item cache to see if we didn't just check the transaction
     *   status a moment ago.
     * ----------------
     */
    if (TransactionIdEquals(transactionId, cachedTestXid))
	return (bool)
	    (status == cachedTestXidStatus);
        
    /* ----------------
     *	compute the item pointer corresponding to the
     *  page containing our transaction id.  We save the item in
     *  our cache to speed up things if we happen to ask for the
     *  same xid's status more than once.
     * ----------------
     */
    TransComputeBlockNumber(LogRelation, transactionId, &blockNumber);
    xidstatus = TransBlockNumberGetXidStatus(LogRelation,
					     blockNumber,
					     transactionId,
					     &fail);

    if (! fail) {
	TransactionIdStore(transactionId, &cachedTestXid);
	cachedTestXidStatus = xidstatus;
	return (bool)
	    (status == xidstatus);
    }
    
    /* ----------------
     *	  here the block didn't contain the information we wanted
     * ----------------
     */
    elog(WARN, "TransactionLogTest: failed to get xidstatus");

    /*
     * so lint is happy...
     */
    return(false);
}

/* --------------------------------
 *	TransactionLogUpdate
 * --------------------------------
 */
void
TransactionLogUpdate(transactionId, status)
    TransactionId 	transactionId;	/* trans id to update */
    XidStatus 		status;		/* new trans status */
{
    BlockNumber		blockNumber;
    bool		fail = false;      	/* success/failure */
    Time 		currentTime;	/* time of this transaction */

    /* ----------------
     * 	during initialization we don't record any updates.
     * ----------------
     */
    if (! RelationIsValid(LogRelation))
	return;
    
    /* ----------------
     *  get the transaction commit time
     * ----------------
     */
    currentTime = GetSystemTime();

    /* ----------------
     *  update the log relation
     * ----------------
     */
    TransComputeBlockNumber(LogRelation, transactionId, &blockNumber);
    TransBlockNumberSetXidStatus(LogRelation,
				 blockNumber,
				 transactionId,
				 status,
				 &fail);

    /* ----------------
     *	 update (invalidate) our single item TransactionLogTest cache.
     * ----------------
     */
    TransactionIdStore(transactionId, &cachedTestXid);
    cachedTestXidStatus = status;
    
    /* ----------------
     *	now we update the time relation, if necessary
     *  (we only record commit times)
     * ----------------
     */
    if (RelationIsValid(TimeRelation) && status == XID_COMMIT) {
	TransComputeBlockNumber(TimeRelation, transactionId, &blockNumber);
	TransBlockNumberSetCommitTime(TimeRelation,
				      blockNumber,
				      transactionId,
				      currentTime,
				      &fail);
	/* ----------------
	 *   update (invalidate) our single item GetCommitTime cache.
	 * ----------------
	 */
	TransactionIdStore(transactionId, &cachedGetCommitTimeXid);
	cachedGetCommitTime = currentTime;
    }

    /* ----------------
     *	now we update the "last committed transaction" field
     *  in the variable relation if we are recording a commit.
     * ----------------
     */
    if (RelationIsValid(VariableRelation) && status == XID_COMMIT)
	UpdateLastCommittedXid(transactionId);
}

/* --------------------------------
 *	TransactionIdGetCommitTime
 * --------------------------------
 */

Time  /* commit time of transaction id */
TransactionIdGetCommitTime(transactionId)
    TransactionId 	transactionId; 	/* transaction id to test */
{
    BlockNumber		blockNumber;
    Time		commitTime;     /* commit time */
    bool		fail = false;      	/* success/failure */
    
    /* ----------------
     *   return invalid if we aren't running yet...
     * ----------------
     */
    if (! RelationIsValid(TimeRelation))
	return InvalidTime;

    /* ----------------
     *	 before going to the buffer manager, check our single
     *   item cache to see if we didn't just get the commit time
     *   a moment ago.
     * ----------------
     */
    if (TransactionIdEquals(transactionId, cachedGetCommitTimeXid))
	return cachedGetCommitTime;
    
    /* ----------------
     *	compute the item pointer corresponding to the
     *  page containing our transaction commit time
     * ----------------
     */
    TransComputeBlockNumber(TimeRelation, transactionId, &blockNumber);
    commitTime = TransBlockNumberGetCommitTime(TimeRelation,
					       blockNumber,
					       transactionId,
					       &fail);

    /* ----------------
     *	update our cache and return the transaction commit time
     * ----------------
     */
    if (! fail) {
	TransactionIdStore(transactionId, &cachedGetCommitTimeXid);
	cachedGetCommitTime = commitTime;
	return commitTime;
    } else
	return InvalidTime;
}

/* ----------------------------------------------------------------
 *		     transaction recovery code
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *	TransRecover
 *
 *    	preform transaction recovery checking.
 *
 *	Note: this should only be preformed if no other backends
 *	      are running.  This is known by the postmaster and
 *	      conveyed by the postmaster passing a "do recovery checking"
 *	      flag to the backend.
 *
 *	here we get the last recorded transaction from the log,
 *	get the "last" and "next" transactions from the variable relation
 *	and then preform some integrity tests:
 *
 *    	1) No transaction may exist higher then the "next" available
 *         transaction recorded in the variable relation.  If this is the
 *         case then it means either the log or the variable relation
 *         has become corrupted.
 *
 *      2) The last committed transaction may not be higher then the
 *         next available transaction for the same reason.
 *
 *      3) The last recorded transaction may not be lower then the
 *         last committed transaction.  (the reverse is ok - it means
 *         that some transactions have aborted since the last commit)
 *
 *	Here is what the proper situation looks like.  The line
 *	represents the data stored in the log.  'c' indicates the
 *      transaction was recorded as committed, 'a' indicates an
 *      abortted transaction and '.' represents information not
 *      recorded.  These may correspond to in progress transactions.
 *
 *	     c  c  a  c  .  .  a  .  .  .  .  .  .  .  .  .  .
 *		      |                 |
 *		     last	       next
 *
 *	Since "next" is only incremented by GetNewTransactionId() which
 *      is called when transactions are started.  Hence if there
 *      are commits or aborts after "next", then it means we committed
 *      or aborted BEFORE we started the transaction.  This is the
 *	rational behind constraint (1).
 *
 *      Likewise, "last" should never greater then "next" for essentially
 *      the same reason - it would imply we committed before we started.
 *      This is the reasoning for (2).
 *
 *	(3) implies we may never have a situation such as:
 *
 *	     c  c  a  c  .  .  a  c  .  .  .  .  .  .  .  .  .
 *		      |                 |
 *		     last	       next
 *
 *      where there is a 'c' greater then "last".
 *
 *      Recovery checking is more difficult in the case where
 *      several backends are executing concurrently because the
 *	transactions may be executing in the other backends.
 *      So, we only do recovery stuff when the backend is explicitly
 *      passed a flag on the command line.
 * --------------------------------
 */

void
TransRecover(logRelation)
    Relation logRelation;
{
    TransactionId logLastXid;
    TransactionId varLastXid;
    TransactionId varNextXid;

#if 0    
    /* ----------------
     *    first get the last recorded transaction in the log.
     * ----------------
     */
    TransGetLastRecordedTransaction(logRelation, logLastXid, &fail);
    if (fail == true)
	elog(WARN, "TransRecover: failed TransGetLastRecordedTransaction");
    
    /* ----------------
     *    next get the "last" and "next" variables
     * ----------------
     */
    VariableRelationGetLastXid(&varLastXid);
    VariableRelationGetNextXid(&varNextXid);

    /* ----------------
     *    intregity test (1)
     * ----------------
     */
    if (TransactionIdIsLessThan(varNextXid, logLastXid))
	elog(WARN, "TransRecover: varNextXid < logLastXid");
	    
    /* ----------------
     *    intregity test (2)
     * ----------------
     */
	    	
    /* ----------------
     *    intregity test (3)
     * ----------------
     */

    /* ----------------
     *  here we have a valid "
     *
     *		**** RESUME HERE ****
     * ----------------
     */
    varNextXid = TransactionIdDup(varLastXid);
    TransactionIdIncrement(&varNextXid);

    VarPut(var, VAR_PUT_LASTXID, varLastXid);
    VarPut(var, VAR_PUT_NEXTXID, varNextXid);
#endif
}
    
/* ----------------------------------------------------------------
 *			Interface functions
 *
 *	InitializeTransactionLog
 *	========
 *	   this function (called near cinit) initializes
 *	   the transaction log, time and variable relations.
 *
 *	TransactionId DidCommit
 *	TransactionId DidAbort
 *	TransactionId IsInProgress
 *	========
 *	   these functions test the transaction status of
 *	   a specified transaction id.
 *
 *	TransactionId Commit
 *	TransactionId Abort
 *	TransactionId SetInProgress
 *	========
 *	   these functions set the transaction status
 *	   of the specified xid. TransactionIdCommit() also
 *	   records the current time in the time relation
 *	   and updates the variable relation counter.
 *
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *	InitializeTransactionLog
 * --------------------------------
 */

void
InitializeTransactionLog()
{
    Relation	  logRelation;
    Relation	  timeRelation;
    Relation	  varRelation;
    MemoryContext oldContext;
    BlockNumber	  n;
    bool	  fail = false;
    
    /* ----------------
     *    don't do anything during bootstrapping
     * ----------------
     */
    if (AMI_OVERRIDE)
	return;
    
    /* ----------------
     *	 disable the transaction system so the access methods
     *   don't interfere during initialization.
     * ----------------
     */
    OverrideTransactionSystem(true);
    
    /* ----------------
     *	make sure allocations occur within the top memory context
     *  so that our log management structures are protected from
     *  garbage collection at the end of every transaction.
     * ----------------
     */
    oldContext = MemoryContextSwitchTo(TopMemoryContext); 
    
    /* ----------------
     *   first open the log and time relations
     *   (these are created by amiint so they are guarantted to exist)
     * ----------------
     */
    logRelation = 	heap_openr(LogRelationName);
    timeRelation = 	heap_openr(TimeRelationName);
    VariableRelation = 	heap_openr(VariableRelationName);
   /* ----------------
    *   XXX TransactionLogUpdate requires that LogRelation
    *	 and TimeRelation are valid so we temporarily set
    *	 them so we can initialize things properly.
    *	 This could be done cleaner.
    * ----------------
    */
    LogRelation =  logRelation;
    TimeRelation = timeRelation;

    /* ----------------
     *   if we have a virgin database, we initialize the log and time
     *	 relation by committing the AmiTransactionId (id 512) and we
     *   initialize the variable relation by setting the next available
     *   transaction id to FirstTransactionId (id 514).  OID initialization
     *   happens as a side effect of bootstrapping in varsup.c.
     * ----------------
     */
    SpinAcquire(OidGenLockId);
    if (!TransactionIdDidCommit(AmiTransactionId)) {

	/* ----------------
	 *  SOMEDAY initialize the information stored in
	 *          the headers of the log/time/variable relations.
	 * ----------------
	 */
	TransactionLogUpdate(AmiTransactionId, XID_COMMIT);
	VariableRelationPutNextXid(FirstTransactionId);
	
    } else if (RecoveryCheckingEnabled()) {
	/* ----------------
	 *	if we have a pre-initialized database and if the
	 *	preform recovery checking flag was passed then we
	 *	do our database integrity checking.
	 * ----------------
	 */
	TransRecover();
    }
    LogRelation =  (Relation) NULL;
    TimeRelation = (Relation) NULL;
    SpinRelease(OidGenLockId);
    
    /* ----------------
     *	now re-enable the transaction system
     * ----------------
     */
    OverrideTransactionSystem(false);
    
    /* ----------------
     *	instantiate the global variables
     * ----------------
     */
    LogRelation = 	logRelation;
    TimeRelation = 	timeRelation;

    /* ----------------
     *	restore the memory context to the previous context
     *  before we return from initialization.
     * ----------------
     */
    MemoryContextSwitchTo(oldContext);
}

/* --------------------------------
 *	TransactionId DidCommit
 *	TransactionId DidAbort
 *	TransactionId IsInProgress
 * --------------------------------
 */

bool	/* true if given transaction committed */
TransactionIdDidCommit(transactionId)
    TransactionId transactionId;
{
    if (AMI_OVERRIDE)
	return true;
    
    return
	TransactionLogTest(transactionId, XID_COMMIT);
}

bool	/* true if given transaction aborted */
TransactionIdDidAbort(transactionId)
    TransactionId transactionId;
{
    if (AMI_OVERRIDE)
	return false;
    
    return
	TransactionLogTest(transactionId, XID_ABORT);
}

bool	/* true if given transaction neither committed nor aborted */
TransactionIdIsInProgress(transactionId)
    TransactionId transactionId;
{
    if (AMI_OVERRIDE)
	return false;
    
    return
	TransactionLogTest(transactionId, XID_INPROGRESS);
}

/* --------------------------------
 *	TransactionId Commit
 *	TransactionId Abort
 *	TransactionId SetInProgress
 * --------------------------------
 */

void
TransactionIdCommit(transactionId)
    TransactionId transactionId;
{
    if (AMI_OVERRIDE)
	return;
    
    TransactionLogUpdate(transactionId, XID_COMMIT);
}

void
TransactionIdAbort(transactionId)
    TransactionId transactionId;
{
    if (AMI_OVERRIDE)
	return;
    
    TransactionLogUpdate(transactionId, XID_ABORT);
}

void
TransactionIdSetInProgress(transactionId)
    TransactionId transactionId;
{
    if (AMI_OVERRIDE)
	return;
    
    TransactionLogUpdate(transactionId, XID_INPROGRESS);
}
