/* ----------------------------------------------------------------
 *   FILE
 *	indextuple.c
 *	
 *   DESCRIPTION
 *	This file contains index tuple accessor and mutator
 *	routines, as well as a few various tuple utilities.
 *
 *   INTERFACE ROUTINES
 *	index_formtuple
 *	index_getsysattr
 *	index_getattr
 *
 *   NOTES
 *	All the stupid ``GeneralResultXXX'' routines should be
 *	eliminated and replaced with something simple and clean.
 *	
 *	old routines formituple, FormIndexTuple, AMgetattr have been
 *	turned into macros in itup.h.  IsValid predicates have been
 *	macroized too. -cim 4/30/91
 *	
 *   IDENTIFICATION
 *	$Header: /private/postgres/src/access/common/RCS/indextuple.c,v 1.14 1992/04/03 01:01:45 mer Exp $
 * ----------------------------------------------------------------
 */
#include "tmp/c.h"
#include "access/ibit.h"
#include "access/itup.h"
#include "access/tupdesc.h"
#include "access/attval.h"
#include "access/tupmacs.h"

#include "storage/form.h"
#include "storage/itemptr.h"
#include "rules/rlock.h"

#include "utils/log.h"
#include "utils/palloc.h"

RcsId("$Header: /private/postgres/src/access/common/RCS/indextuple.c,v 1.14 1992/04/03 01:01:45 mer Exp $");

/* ----------------------------------------------------------------
 *		  index_ tuple interface routines
 * ----------------------------------------------------------------
 */

/* ----------------
 *	index_formtuple
 * ----------------
 */
IndexTuple
index_formtuple(numberOfAttributes, tupleDescriptor, value, null)
    AttributeNumber	numberOfAttributes;
    TupleDescriptor	tupleDescriptor;
    Datum		value[];
    char		null[];
{
    register char	*tp;	/* tuple pointer */
    IndexTuple		tuple;	/* return tuple */
    Size		size, hoff;
    int 		i, infomask = 0;
    bool		hasnull = false;
    char		tupmask = 0;

    if (numberOfAttributes > MaxIndexAttributeNumber)
	elog(WARN, "index_formtuple: numberOfAttributes of %d > %d",
	     numberOfAttributes, MaxIndexAttributeNumber);
    

    for (i = 0; i < numberOfAttributes && !hasnull; i++)
    {
	if (null[i] != ' ') hasnull = true;
    }

    if (hasnull) infomask |= INDEX_NULL_MASK;

    hoff = IndexInfoFindDataOffset(infomask);
    size = hoff
	 + ComputeDataSize(numberOfAttributes, tupleDescriptor, value, null);
    size = LONGALIGN(size);

    tp = (char *) palloc(size);
    tuple = (IndexTuple) tp;
    bzero(tp, (int)size);
    
    DataFill((Pointer) tp + hoff,
	     numberOfAttributes,
	     tupleDescriptor,
	     value,
	     null,
	     &tupmask,
	     (hasnull ? tp + sizeof *tuple : NULL));

    /*
     * We do this because DataFill wants to initialize a "tupmask" which
     * is used for HeapTuples, but we want an indextuple infomask.  The only
     * "relevent" info is the "has variable attributes" field, which is in
     * mask position 0x02.  We have already set the null mask above.
     */

    if (tupmask & 0x02) infomask |= INDEX_VAR_MASK;

    /*
     * Here we make sure that we can actually hold the size.  We also want
     * to make sure that size is not aligned oddly.  This actually is a
     * rather odd way to make sure the size is not too large overall.
     */

    if (size & 0xE000)
    {
	elog(WARN, "index_formtuple: data takes %d bytes: too big", size);
    }

    infomask |= size;

    /* ----------------
     * initialize metadata
     * ----------------
     */
    tuple->t_info = infomask;
    return (tuple);
}

/* ----------------
 *	fastgetiattr
 *
 *	This is a newer version of fastgetiattr which attempts to be
 *	faster by caching attribute offsets in the attribute descriptor.
 *
 *	an alternate way to speed things up would be to cache offsets
 *	with the tuple, but that seems more difficult unless you take
 *	the storage hit of actually putting those offsets into the
 *	tuple you send to disk.  Yuck.
 *
 *	This scheme will be slightly slower than that, but should
 *	preform well for queries which hit large #'s of tuples.  After
 *	you cache the offsets once, examining all the other tuples using
 *	the same attribute descriptor will go much quicker. -cim 5/4/91
 * ----------------
 */

char *
fastgetiattr(tup, attnum, att, isnull)
    IndexTuple	tup;
    unsigned	attnum;
    struct	attribute *att[];
    bool	*isnull;
{
    register char		*tp;		/* ptr to att in tuple */
    register char		*bp;		/* ptr to att in tuple */
    int 			slow;		/* do we have to walk nulls? */
    register int		data_off;	/* tuple data offset */

    /* ----------------
     *	sanity checks
     * ----------------
     */

    Assert(PointerIsValid(isnull));
    Assert(attnum > 0);

    /* ----------------
     *   Three cases:
     * 
     *   1: No nulls and no variable length attributes.
     *   2: Has a null or a varlena AFTER att.
     *   3: Has nulls or varlenas BEFORE att.
     * ----------------
     */

    *isnull =  false;
    data_off = IndexTupleHasMinHeader(tup) ? sizeof *tup : 
               IndexInfoFindDataOffset(tup->t_info);

    if (IndexTupleNoNulls(tup))
    {
	/* first attribute is always at position zero */

	if (attnum == 1)
	{
 	    return(fetchatt(att, (Pointer) tup + data_off));
	}
	attnum--;

	if (att[attnum]->attcacheoff > 0)
	{
	    return(fetchatt(att + attnum, (Pointer) tup + data_off
				+ att[attnum]->attcacheoff));
	}

	tp = (Pointer) tup + data_off;

	slow = 0;
    }
    else /* there's a null somewhere in the tuple */
    {
	bp = (char *) tup + sizeof(*tup); /* "knows" t_bits are here! */
	slow = 0;
        /* ----------------
         *	check to see if desired att is null
         * ----------------
         */

	attnum--;
	{
	    if (att_isnull(attnum, bp)) 
	    {
		*isnull = true;
		return NULL;
	    }
	}
        /* ----------------
	 *      Now check to see if any preceeding bits are null...
         * ----------------
	 */
	{
	    register int  i = 0; /* current offset in bp */
	    register int  mask;	 /* bit in byte we're looking at */
	    register char n;	 /* current byte in bp */
	    register int byte, finalbit;
	
	    byte = attnum >> 3;
	    finalbit = attnum & 0x07;

	    for (; i <= byte; i++) {
	        n = bp[i];
	        if (i < byte) {
		    /* check for nulls in any "earlier" bytes */
		    if ((~n) != 0) {
		        slow++;
		        break;
		    }
	        } else {
		    /* check for nulls "before" final bit of last byte*/
		    mask = (finalbit << 1) - 1;
		    if ((~n) & mask)
		        slow++;
	        }
	    }
        }
	tp = (Pointer) tup + data_off;
    }

    /* now check for any non-fixed length attrs before our attribute */

    if (!slow)
    {
	if (att[attnum]->attcacheoff > 0)
	{
	    return(fetchatt(att + attnum, tp + att[attnum]->attcacheoff));
	}
	else if (!IndexTupleAllFixed(tup))
	{
	    register int j = 0;

	    for (j = 0; j < attnum && !slow; j++)
		if (att[j]->attlen < 1) slow = 1;
	}
    }

    /*
     * if slow is zero, and we got here, we know that we have a tuple with
     * no nulls.  We also know that we have to initialize the remainder of
     * the attribute cached offset values.
     */

    if (!slow)
    {
	register int j = 1;
	register long off;

	/*
	 * need to set cache for some atts
	 */

	att[0]->attcacheoff = 0;

	while (att[j]->attcacheoff > 0) j++;

	off = att[j-1]->attcacheoff + att[j-1]->attlen;

	for (; j < attnum + 1; j++)
	{
	    /*
	     * Fix me when going to a machine with more than a four-byte
	     * word!
	     */

	    switch(att[j]->attlen)
	    {
		case sizeof(char) : break;
		case sizeof(short): off = SHORTALIGN(off); break;
		default           : off = LONGALIGN(off); break;
	    }

	    att[j]->attcacheoff = off;
	    off += att[j]->attlen;
	}

	return(fetchatt(att + attnum, tp + att[attnum]->attcacheoff));
    }
    else
    {
	register bool usecache = true;
	register int off = 0;
	register int savelen;
	register int i;

	/*
	 * Now we know that we have to walk the tuple CAREFULLY.
	 */
	
	for (i = 0; i < attnum; i++)
	{
	    if (!IndexTupleNoNulls(tup))
	    {
		if (att_isnull(i, bp))
		{
		    usecache = false;
		    continue;
		}
	    }

	    if (usecache && att[i]->attcacheoff > 0)
	    {
		off = att[i]->attcacheoff;
		if (att[i]->attlen == -1)
		{
		    usecache = false;
		}
		else continue;
	    }

	    if (usecache) att[i]->attcacheoff = off;
	    switch(att[i]->attlen)
	    {
	        case sizeof(char):
	            off++;
	            break;
	        case sizeof(short):
	            off = SHORTALIGN(off + sizeof(short));
	            break;
	        case -1:
	            usecache = false;
		    off = LONGALIGN(off);
	    	    off += VARSIZE(tp + off);
		    break;
		default:
		    off = LONGALIGN(off + att[i]->attlen);
		    break;
	    }
	}

	return(fetchatt(att + attnum, tp + off));
    }
}
/* ----------------
 *	index_getsysattr
 * ----------------
 */
AttributeValue
index_getsysattr(tuple, attributeNumber)
    IndexTuple		tuple;
    int			attributeNumber;
{
    static Datum	datum;
    
    switch (attributeNumber) {
    case IndxRuleLockAttributeNumber:
	elog(NOTICE, "index_getsysattr: Rule locks are always NULL");
	return PointerGetDatum((Pointer) NULL);
	break;
	
    case IndxBaseTupleIdAttributeNumber:
	bcopy((char *) &tuple->t_tid, (char *) &datum, sizeof datum);
	return datum;
	break;
	
    default:
	elog(WARN, "IndexTupleGetAttributeValue: undefined attnum %d",
	     attributeNumber);
    }
    return((AttributeValue) NULL);
}

/* ----------------
 *	index_getattr
 * ----------------
 */
AttributeValue
IndexTupleGetAttributeValue(tuple, attNum, tupleDescriptor, isNullOutP)
    IndexTuple		tuple;
    AttributeNumber		attNum;
    TupleDescriptor	tupleDescriptor;
    Boolean		*isNullOutP;
{
    /* ----------------
     *	handle normal attributes
     * ----------------
     */
    if (attNum > 0)
	return (AttributeValue)
	    fastgetiattr(tuple, attNum, tupleDescriptor, isNullOutP);

    /* ----------------
     *	otherwise handle system attributes
     * ----------------
     */
    *isNullOutP = '\0';
    return
	index_getsysattr(tuple, attNum);
}

Pointer
index_getattr(tuple, attNum, tupDesc, isNullOutP)
    IndexTuple		tuple;
    AttributeNumber	attNum;
    TupleDescriptor	tupDesc;
    Boolean		*isNullOutP;
{
    Datum  datum;
    datum = IndexTupleGetAttributeValue(tuple, attNum, tupDesc, isNullOutP);
    
    return
	DatumGetPointer(datum);
}

/* ----------------------------------------------------------------
 *	      misc index result stuff (XXX OBSOLETE ME!)
 * ----------------------------------------------------------------
 */

GeneralInsertIndexResult
ItemPointerFormGeneralInsertIndexResult(pointer, lock)
    ItemPointer	pointer;
    RuleLock	lock;
{
    GeneralInsertIndexResult	result;

    Assert(ItemPointerIsValid(pointer));
    /* XXX Assert(RuleLockIsValid(lock)); locks don't work yet */

    result = (GeneralInsertIndexResult) palloc(sizeof *result);

    result->pointerData = *pointer;
    result->lock = lock;
    return (result);
}

InsertIndexResult
ItemPointerFormInsertIndexResult(pointer, lock, offset)
    ItemPointer	pointer;
    RuleLock	lock;
    double		offset;
{
    InsertIndexResult	result;

    Assert(ItemPointerIsValid(pointer));
    /* XXX Assert(RuleLockIsValid(lock)); locks don't work yet */
    /* Assert(InsertOffsetIsValid(offset)); */

    result = (InsertIndexResult) palloc(sizeof *result);

    result->pointerData = *pointer;
    result->lock = lock;
    result->offset = offset;
    return (result);
}

GeneralRetrieveIndexResult
ItemPointerFormGeneralRetrieveIndexResult(heapItemPointer)
    ItemPointer	heapItemPointer;
{
    GeneralRetrieveIndexResult	result;
    Assert(ItemPointerIsValid(heapItemPointer));

    result = (GeneralRetrieveIndexResult) palloc(sizeof *result);
    result->heapItemData = *heapItemPointer;
    return (result);
}


RetrieveIndexResult
ItemPointerFormRetrieveIndexResult(indexItemPointer, heapItemPointer)
    ItemPointer	indexItemPointer;
    ItemPointer	heapItemPointer;
{
    RetrieveIndexResult	result;

    Assert(ItemPointerIsValid(indexItemPointer));
    Assert(ItemPointerIsValid(heapItemPointer));

    result = (RetrieveIndexResult) palloc(sizeof *result);

    result->indexItemData = *indexItemPointer;
    result->heapItemData = *heapItemPointer;

    return (result);
}

/*
 * Takes an infomask as argument (primarily because this needs to be usable
 * at index_formtuple time so enough space is allocated).
 *
 * Change me if adding an attribute to IndexTuples!!!!!!!!!!!
 */

Size
IndexInfoFindDataOffset(t_info)

unsigned short t_info;

{
    if (!(t_info & (INDEX_NULL_MASK | INDEX_RULE_MASK)))
	return((Size) sizeof(IndexTupleData));
    else
    {
	Size size = sizeof(IndexTupleData);

	if (t_info & INDEX_NULL_MASK)
	{
	    size += sizeof(IndexAttributeBitMapData);
	}
	if (t_info & INDEX_RULE_MASK)
	{
	    size = LONGALIGN(size) + sizeof(IndexTupleRuleLock) + sizeof(char);
	}
	return(LONGALIGN(size));
    }
}

/*
 * Copies source into target.  If *target == NULL, we palloc space; otherwise
 * we assume we have space that is already palloc'ed.
 */

void
CopyIndexTuple(source, target)

IndexTuple source, *target;

{
    Size size, hoff;
    IndexTuple ret;

    size = IndexTupleSize(source);
    if (*target == NULL)
    {
	*target = ret = (IndexTuple) palloc(size);
    }

    bcopy(source, ret, size);
}

/*
    for debugging purpose only.  will only be called in dbx.
*/
int
indexTupleHeaderSize()
{
    return(sizeof(IndexTupleData));
}
