/*-------------------------------------------------------------------------
 *
 * transsup.c--
 *    postgres transaction access method support code
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    $Header: /usr/local/devel/pglite/cvs/src/backend/access/transam/transsup.c,v 1.3 1995/03/03 07:16:11 andrew Exp $
 *
 * NOTES
 *    This file contains support functions for the high
 *    level access method interface routines found in transam.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

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

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

#include "utils/rel.h"
#include "utils/elog.h"
#include "utils/memutils.h"
#include "utils/nabstime.h"

#include "catalog/heap.h"
#include "access/transam.h"	/* where the declarations go */

#include "storage/smgr.h"

/* ----------------------------------------------------------------
 *		      general support routines
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *	AmiTransactionOverride
 *
 *	This function is used to manipulate the bootstrap flag.
 * --------------------------------
 */
void
AmiTransactionOverride(bool flag)
{
    AMI_OVERRIDE = flag;
}

/* --------------------------------
 *	TransComputeBlockNumber
 * --------------------------------
 */
void
TransComputeBlockNumber(Relation relation, /* relation to test */
			TransactionId transactionId, /* transaction id to test */
			BlockNumber *blockNumberOutP)
{
    long	itemsPerBlock;
    
    /* ----------------
     *  we calculate the block number of our transaction
     *  by dividing the transaction id by the number of
     *  transaction things per block.  
     * ----------------
     */
    if (relation == LogRelation)
	itemsPerBlock = TP_NumXidStatusPerBlock;
    else if (relation == TimeRelation)
	itemsPerBlock = TP_NumTimePerBlock;
    else
	elog(WARN, "TransComputeBlockNumber: unknown relation");
    
    /* ----------------
     *	warning! if the transaction id's get too large
     *  then a BlockNumber may not be large enough to hold the results
     *  of our division.
     *
     *	XXX  this will all vanish soon when we implement an improved
     *       transaction id schema -cim 3/23/90
     *
     *  This has vanished now that xid's are 4 bytes (no longer 5).
     *  -mer 5/24/92
     * ----------------
     */
    (*blockNumberOutP) = transactionId / itemsPerBlock;
}


/* ----------------------------------------------------------------
 *		     trans block support routines
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *	TransBlockGetLastTransactionIdStatus
 *
 *	This returns the status and transaction id of the last
 *	transaction information recorded on the given TransBlock.
 * --------------------------------
 */

XidStatus
TransBlockGetLastTransactionIdStatus(Block tblock,
				     TransactionId baseXid,
				     TransactionId *returnXidP)
{
    Index         index;
    Index         maxIndex;
    bits8         bit1;
    bits8	  bit2;
    BitIndex      offset;
    XidStatus     xstatus;
    
    /* ----------------
     *	sanity check
     * ----------------
     */
    Assert((tblock != NULL));
    Assert((tblock->data != NULL));
    
    
    /* ----------------
     *	search downward from the top of the block data, looking
     *  for the first Non-in progress transaction status.  Since we
     *  are scanning backward, this will be last recorded transaction
     *  status on the block.
     * ----------------
     */
    maxIndex = TP_NumXidStatusPerBlock;
    for (index = maxIndex-1; index>=0; index--) {
	offset =  BitIndexOf(index);
	bit1 =    ((bits8) BitArrayBitIsSet((BitArray) tblock->data, offset++)) << 1;
	bit2 =    (bits8)  BitArrayBitIsSet((BitArray) tblock->data, offset);
	
	xstatus =  (bit1 | bit2) ;
	
	/* ----------------
	 *  here we have the status of some transaction, so test
	 *  if the status is recorded as "in progress".  If so, then
	 *  we save the transaction id in the place specified by the caller.
	 * ----------------
	 */
	if (xstatus != XID_INPROGRESS) {
	    if (returnXidP != NULL) {
		TransactionIdStore(baseXid, returnXidP);
		TransactionIdAdd(returnXidP, index);
	    }
	    break;
	}
    }
    
    /* ----------------
     *	if we get here and index is 0 it means we couldn't find
     *  a non-inprogress transaction on the block.  For now we just
     *  return this info to the user.  They can check if the return
     *  status is "in progress" to know this condition has arisen.
     * ----------------
     */
    if (index == 0) {
	if (returnXidP != NULL)
	    TransactionIdStore(baseXid, returnXidP);
    }
    
    /* ----------------
     *	return the status to the user
     * ----------------
     */
    return xstatus;
}

/* --------------------------------
 *	TransBlockGetXidStatus
 *
 *	This returns the status of the desired transaction
 * --------------------------------
 */

XidStatus
TransBlockGetXidStatus(Block tblock,
		       TransactionId transactionId)
{
    Index       		index;
    bits8       		bit1;
    bits8			bit2;
    BitIndex    		offset;
    
    /* ----------------
     *	sanity check
     * ----------------
     */
    if (tblock == NULL || tblock->data == NULL) {
	return XID_INVALID;
    }
    
    /* ----------------
     *	calculate the index into the transaction data where
     *  our transaction status is located
     *
     *  XXX this will be replaced soon when we move to the
     *      new transaction id scheme -cim 3/23/90
     *
     *  The old system has now been replaced. -mer 5/24/92
     * ----------------
     */
    index = transactionId % TP_NumXidStatusPerBlock;
    
    /* ----------------
     *	get the data at the specified index
     * ----------------
     */
    offset =    BitIndexOf(index);
    bit1 =      ((bits8)   BitArrayBitIsSet((BitArray) tblock->data, offset++)) << 1;
    bit2 =      (bits8)    BitArrayBitIsSet((BitArray) tblock->data, offset);
    
    /* ----------------
     *	return the transaction status to the caller
     * ----------------
     */
    return (XidStatus)
	(bit1 | bit2);
}

/* --------------------------------
 *	TransBlockSetXidStatus
 *
 *	This sets the status of the desired transaction
 * --------------------------------
 */
void
TransBlockSetXidStatus(Block tblock,
		       TransactionId transactionId, 
		       XidStatus xstatus)
{
    Index       		index;
    BitIndex    		offset;
    
    /* ----------------
     *	sanity check
     * ----------------
     */
    if (tblock == NULL || tblock->data == NULL)
	return;
    
    /* ----------------
     *	calculate the index into the transaction data where
     *  we sould store our transaction status.
     *
     *  XXX this will be replaced soon when we move to the
     *      new transaction id scheme -cim 3/23/90
     *
     *  The new scheme is here -mer 5/24/92
     * ----------------
     */
    index = transactionId % TP_NumXidStatusPerBlock;
    
    offset =    BitIndexOf(index);
    
    /* ----------------
     *	store the transaction value at the specified offset
     * ----------------
     */
    switch(xstatus) {
    case XID_COMMIT:             /* set 10 */
	BitArraySetBit((BitArray) tblock->data, offset);
	BitArrayClearBit((BitArray) tblock->data, offset + 1);
	break;
    case XID_ABORT:             /* set 01 */
	BitArrayClearBit((BitArray) tblock->data, offset);
	BitArraySetBit((BitArray) tblock->data, offset + 1);
	break;
    case XID_INPROGRESS:        /* set 00 */
	BitArrayClearBit((BitArray) tblock->data, offset);
	BitArrayClearBit((BitArray) tblock->data, offset + 1);
	break;
    default:
	elog(NOTICE,
	     "TransBlockSetXidStatus: invalid status: %d (ignored)",
	     xstatus);
	break;
    }
}

/* --------------------------------
 *	TransBlockGetCommitTime
 *
 *	This returns the transaction commit time for the
 *	specified transaction id in the trans block.
 * --------------------------------
 */
AbsoluteTime
TransBlockGetCommitTime(Block tblock,
			TransactionId transactionId)
{
    Index			index;
    AbsoluteTime		*timeArray;
    
    /* ----------------
     *	sanity check
     * ----------------
     */
    if (tblock == NULL || tblock->data == NULL)
	return INVALID_ABSTIME;
    
    /* ----------------
     *	calculate the index into the transaction data where
     *  our transaction commit time is located
     *
     *  XXX this will be replaced soon when we move to the
     *      new transaction id scheme -cim 3/23/90
     *
     *  The new scheme is here. -mer 5/24/92
     * ----------------
     */
    index = transactionId % TP_NumTimePerBlock;
    
    /* ----------------
     *	return the commit time to the caller
     * ----------------
     */
    timeArray =  (AbsoluteTime *) tblock->data;
    return (AbsoluteTime)
	timeArray[ index ];
}

/* --------------------------------
 *	TransBlockSetCommitTime
 *
 *	This sets the commit time of the specified transaction
 * --------------------------------
 */
void
TransBlockSetCommitTime(Block tblock,
			TransactionId transactionId,
			AbsoluteTime commitTime)
{
    Index		index;
    AbsoluteTime	*timeArray;
    
    /* ----------------
     *	sanity check
     * ----------------
     */
    if (tblock == NULL || tblock->data == NULL)
	return;
    
    
    /* ----------------
     *	calculate the index into the transaction data where
     *  we sould store our transaction status.  
     *
     *  XXX this will be replaced soon when we move to the
     *      new transaction id scheme -cim 3/23/90
     *
     *  The new scheme is here.  -mer 5/24/92
     * ----------------
     */
    index = transactionId % TP_NumTimePerBlock;
    
    /* ----------------
     *	store the transaction commit time at the specified index
     * ----------------
     */
    timeArray =  (AbsoluteTime *) tblock->data;
    timeArray[ index ] = commitTime;
}

/* ----------------------------------------------------------------
 *	           transam i/o support routines
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *	TransBlockNumberGetXidStatus
 * --------------------------------
 */
XidStatus
TransBlockNumberGetXidStatus(Relation relation,
			     BlockNumber blockNumber,
			     TransactionId xid,
			     bool *failP)
{
    Buffer	   	buffer;		/* buffer associated with block */
    Block		block;		/* block containing xstatus */
    XidStatus		xstatus;	/* recorded status of xid */
    bool		localfail;      /* bool used if failP = NULL */
    
    /* ----------------
     *	SOMEDAY place a read lock on the log relation
     *  That someday is today 5 Aug 1991 -mer
     * ----------------
     */
    RelationSetLockForRead(relation);
    
    /* ----------------
     *	get the page containing the transaction information
     * ----------------
     */
    buffer = 	   ReadBuffer(relation, blockNumber);
    block =   	   BufferGetBlock(buffer);
    
    /* ----------------
     *	get the status from the block.  note, for now we always
     *  return false in failP.
     * ----------------
     */
    if (failP == NULL)
	failP = &localfail;
    (*failP) = false;
    
    xstatus = TransBlockGetXidStatus(block, xid);
    
    /* ----------------
     *	release the buffer and return the status
     * ----------------
     */
    ReleaseBuffer(buffer);
    
    /* ----------------
     *	SOMEDAY release our lock on the log relation
     * ----------------
     */
    RelationUnsetLockForRead(relation);
    
    return
	xstatus;
}

/* --------------------------------
 *	TransBlockNumberSetXidStatus
 * --------------------------------
 */
void
TransBlockNumberSetXidStatus(Relation relation,
			     BlockNumber blockNumber,
			     TransactionId xid,
			     XidStatus xstatus,
			     bool *failP)
{
    Buffer	   	buffer;		/* buffer associated with block */
    Block		block;		/* block containing xstatus */
    bool		localfail;      /* bool used if failP = NULL */
    
    /* ----------------
     *	SOMEDAY gain exclusive access to the log relation
     *
     *  That someday is today 5 Aug 1991 -mer
     * ----------------
     */
    RelationSetLockForWrite(relation);
    
    /* ----------------
     *	get the block containing the transaction status
     * ----------------
     */
    buffer = 	ReadBuffer(relation, blockNumber);
    block =   	BufferGetBlock(buffer);
    
    /* ----------------
     *	attempt to update the status of the transaction on the block.
     *  if we are successful, write the block. otherwise release the buffer.
     *  note, for now we always return false in failP.
     * ----------------
     */
    if (failP == NULL)
	failP = &localfail;
    (*failP) = false;
    
    TransBlockSetXidStatus(block, xid, xstatus);
    
    if ((*failP) == false)
	WriteBuffer(buffer);
    else
	ReleaseBuffer(buffer);
    
    /* ----------------
     *	SOMEDAY release our lock on the log relation
     * ----------------
     */    
    RelationUnsetLockForWrite(relation);
}

/* --------------------------------
 *	TransBlockNumberGetCommitTime
 * --------------------------------
 */
AbsoluteTime
TransBlockNumberGetCommitTime(Relation relation,
			      BlockNumber blockNumber,
			      TransactionId xid,
			      bool *failP)
{
    Buffer	   	buffer;		/* buffer associated with block */
    Block		block;		/* block containing commit time */
    bool		localfail;      /* bool used if failP = NULL */
    AbsoluteTime	xtime;		/* commit time */
    
    /* ----------------
     *	SOMEDAY place a read lock on the time relation
     *
     *  That someday is today 5 Aug. 1991 -mer
     * ----------------
     */
    RelationSetLockForRead(relation);
    
    /* ----------------
     *	get the block containing the transaction information
     * ----------------
     */
    buffer = 		ReadBuffer(relation, blockNumber);
    block =   		BufferGetBlock(buffer);
    
    /* ----------------
     *	get the commit time from the block
     *  note, for now we always return false in failP.
     * ----------------
     */
    if (failP == NULL)
	failP = &localfail;
    (*failP) = false;
    
    xtime = TransBlockGetCommitTime(block, xid);
    
    /* ----------------
     *	release the buffer and return the commit time
     * ----------------
     */
    ReleaseBuffer(buffer);
    
    /* ----------------
     *	SOMEDAY release our lock on the time relation
     * ----------------
     */
    RelationUnsetLockForRead(relation);
    
    if ((*failP) == false)
	return xtime;
    else
	return INVALID_ABSTIME;
    
}

/* --------------------------------
 *	TransBlockNumberSetCommitTime
 * --------------------------------
 */
void
TransBlockNumberSetCommitTime(Relation relation,
			      BlockNumber blockNumber,
			      TransactionId xid,
			      AbsoluteTime xtime,
			      bool *failP)
{
    Buffer	   	buffer;		/* buffer associated with block */
    Block		block;		/* block containing commit time */
    bool		localfail;      /* bool used if failP = NULL */
    
    /* ----------------
     *	SOMEDAY gain exclusive access to the time relation
     *
     *  That someday is today 5 Aug. 1991 -mer
     * ----------------
     */
    RelationSetLockForWrite(relation);
    
    /* ----------------
     *	get the block containing our commit time
     * ----------------
     */
    buffer = 	   ReadBuffer(relation, blockNumber);
    block =   	   BufferGetBlock(buffer);
    
    /* ----------------
     *	attempt to update the commit time of the transaction on the block.
     *  if we are successful, write the block. otherwise release the buffer.
     *  note, for now we always return false in failP.
     * ----------------
     */
    if (failP == NULL)
	failP = &localfail;
    (*failP) = false;
    
    TransBlockSetCommitTime(block, xid, xtime);
    
    if ((*failP) == false)
	WriteBuffer(buffer);
    else
	ReleaseBuffer(buffer);
    
    /* ----------------
     *	SOMEDAY release our lock on the time relation
     * ----------------
     */
    RelationUnsetLockForWrite(relation);
    
}

/* --------------------------------
 *	TransGetLastRecordedTransaction
 * --------------------------------
 */
void
TransGetLastRecordedTransaction(Relation relation,
				TransactionId xid, /* return: transaction id */
				bool *failP)
{
    BlockNumber	  	blockNumber;	/* block number */
    Buffer	   	buffer;		/* buffer associated with block */
    Block		block;		/* block containing xid status */
    BlockNumber		n;		/* number of blocks in the relation */
    TransactionId	baseXid;
    
    (*failP) = false;
    
    /* ----------------
     *	SOMEDAY gain exclusive access to the log relation
     *
     *  That someday is today 5 Aug. 1991 -mer
     *  It looks to me like we only need to set a read lock here, despite
     *  the above comment about exclusive access.  The block is never 
     *  actually written into, we only check status bits.
     * ----------------
     */
    RelationSetLockForRead(relation);
    
    /* ----------------
     *	we assume the last block of the log contains the last
     *  recorded transaction.  If the relation is empty we return
     *  failure to the user.
     * ----------------
     */
    n = RelationGetNumberOfBlocks(relation);
    if (n == 0) {
	(*failP) = true;
	return;
    }
    
    /* ----------------
     *	get the block containing the transaction information
     * ----------------
     */
    blockNumber =  n-1;
    buffer = 	ReadBuffer(relation, blockNumber);
    block =   	BufferGetBlock(buffer);
    
    /* ----------------
     *	get the last xid on the block
     * ----------------
     */
    baseXid = blockNumber * TP_NumXidStatusPerBlock;

/* XXX ???? xid won't get returned! - AY '94 */
    (void) TransBlockGetLastTransactionIdStatus(block, baseXid, &xid);
    
    ReleaseBuffer(buffer);
    
    /* ----------------
     *	SOMEDAY release our lock on the log relation
     * ----------------
     */
    RelationUnsetLockForRead(relation);
}
