/*-------------------------------------------------------------------------
 *
 * rtget.c
 *	  fetch tuples from an rtree scan.
 *
 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/access/rtree/rtget.c,v 1.25 2001/03/22 03:59:16 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/iqual.h"
#include "access/relscan.h"
#include "access/rtree.h"

static OffsetNumber findnext(IndexScanDesc s, Page p, OffsetNumber n,
		 ScanDirection dir);
static RetrieveIndexResult rtscancache(IndexScanDesc s, ScanDirection dir);
static RetrieveIndexResult rtfirst(IndexScanDesc s, ScanDirection dir);
static RetrieveIndexResult rtnext(IndexScanDesc s, ScanDirection dir);
static ItemPointer rtheapptr(Relation r, ItemPointer itemp);


Datum
rtgettuple(PG_FUNCTION_ARGS)
{
	IndexScanDesc s = (IndexScanDesc) PG_GETARG_POINTER(0);
	ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
	RetrieveIndexResult res;

	/* if we have it cached in the scan desc, just return the value */
	if ((res = rtscancache(s, dir)) != (RetrieveIndexResult) NULL)
		PG_RETURN_POINTER(res);

	/* not cached, so we'll have to do some work */
	if (ItemPointerIsValid(&(s->currentItemData)))
		res = rtnext(s, dir);
	else
		res = rtfirst(s, dir);
	PG_RETURN_POINTER(res);
}

static RetrieveIndexResult
rtfirst(IndexScanDesc s, ScanDirection dir)
{
	Buffer		b;
	Page		p;
	OffsetNumber n;
	OffsetNumber maxoff;
	RetrieveIndexResult res;
	RTreePageOpaque po;
	RTreeScanOpaque so;
	RTSTACK    *stk;
	BlockNumber blk;
	IndexTuple	it;

	b = ReadBuffer(s->relation, P_ROOT);
	p = BufferGetPage(b);
	po = (RTreePageOpaque) PageGetSpecialPointer(p);
	so = (RTreeScanOpaque) s->opaque;

	for (;;)
	{
		maxoff = PageGetMaxOffsetNumber(p);
		if (ScanDirectionIsBackward(dir))
			n = findnext(s, p, maxoff, dir);
		else
			n = findnext(s, p, FirstOffsetNumber, dir);

		while (n < FirstOffsetNumber || n > maxoff)
		{

			ReleaseBuffer(b);
			if (so->s_stack == (RTSTACK *) NULL)
				return (RetrieveIndexResult) NULL;

			stk = so->s_stack;
			b = ReadBuffer(s->relation, stk->rts_blk);
			p = BufferGetPage(b);
			po = (RTreePageOpaque) PageGetSpecialPointer(p);
			maxoff = PageGetMaxOffsetNumber(p);

			if (ScanDirectionIsBackward(dir))
				n = OffsetNumberPrev(stk->rts_child);
			else
				n = OffsetNumberNext(stk->rts_child);
			so->s_stack = stk->rts_parent;
			pfree(stk);

			n = findnext(s, p, n, dir);
		}
		if (po->flags & F_LEAF)
		{
			ItemPointerSet(&(s->currentItemData), BufferGetBlockNumber(b), n);

			it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));

			res = FormRetrieveIndexResult(&(s->currentItemData), &(it->t_tid));

			ReleaseBuffer(b);
			return res;
		}
		else
		{
			stk = (RTSTACK *) palloc(sizeof(RTSTACK));
			stk->rts_child = n;
			stk->rts_blk = BufferGetBlockNumber(b);
			stk->rts_parent = so->s_stack;
			so->s_stack = stk;

			it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
			blk = ItemPointerGetBlockNumber(&(it->t_tid));

			ReleaseBuffer(b);
			b = ReadBuffer(s->relation, blk);
			p = BufferGetPage(b);
			po = (RTreePageOpaque) PageGetSpecialPointer(p);
		}
	}
}

static RetrieveIndexResult
rtnext(IndexScanDesc s, ScanDirection dir)
{
	Buffer		b;
	Page		p;
	OffsetNumber n;
	OffsetNumber maxoff;
	RetrieveIndexResult res;
	RTreePageOpaque po;
	RTreeScanOpaque so;
	RTSTACK    *stk;
	BlockNumber blk;
	IndexTuple	it;

	blk = ItemPointerGetBlockNumber(&(s->currentItemData));
	n = ItemPointerGetOffsetNumber(&(s->currentItemData));

	if (ScanDirectionIsForward(dir))
		n = OffsetNumberNext(n);
	else
		n = OffsetNumberPrev(n);

	b = ReadBuffer(s->relation, blk);
	p = BufferGetPage(b);
	po = (RTreePageOpaque) PageGetSpecialPointer(p);
	so = (RTreeScanOpaque) s->opaque;

	for (;;)
	{
		maxoff = PageGetMaxOffsetNumber(p);
		n = findnext(s, p, n, dir);

		while (n < FirstOffsetNumber || n > maxoff)
		{

			ReleaseBuffer(b);
			if (so->s_stack == (RTSTACK *) NULL)
				return (RetrieveIndexResult) NULL;

			stk = so->s_stack;
			b = ReadBuffer(s->relation, stk->rts_blk);
			p = BufferGetPage(b);
			maxoff = PageGetMaxOffsetNumber(p);
			po = (RTreePageOpaque) PageGetSpecialPointer(p);

			if (ScanDirectionIsBackward(dir))
				n = OffsetNumberPrev(stk->rts_child);
			else
				n = OffsetNumberNext(stk->rts_child);
			so->s_stack = stk->rts_parent;
			pfree(stk);

			n = findnext(s, p, n, dir);
		}
		if (po->flags & F_LEAF)
		{
			ItemPointerSet(&(s->currentItemData), BufferGetBlockNumber(b), n);

			it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));

			res = FormRetrieveIndexResult(&(s->currentItemData), &(it->t_tid));

			ReleaseBuffer(b);
			return res;
		}
		else
		{
			stk = (RTSTACK *) palloc(sizeof(RTSTACK));
			stk->rts_child = n;
			stk->rts_blk = BufferGetBlockNumber(b);
			stk->rts_parent = so->s_stack;
			so->s_stack = stk;

			it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
			blk = ItemPointerGetBlockNumber(&(it->t_tid));

			ReleaseBuffer(b);
			b = ReadBuffer(s->relation, blk);
			p = BufferGetPage(b);
			po = (RTreePageOpaque) PageGetSpecialPointer(p);

			if (ScanDirectionIsBackward(dir))
				n = PageGetMaxOffsetNumber(p);
			else
				n = FirstOffsetNumber;
		}
	}
}

static OffsetNumber
findnext(IndexScanDesc s, Page p, OffsetNumber n, ScanDirection dir)
{
	OffsetNumber maxoff;
	IndexTuple	it;
	RTreePageOpaque po;
	RTreeScanOpaque so;

	maxoff = PageGetMaxOffsetNumber(p);
	po = (RTreePageOpaque) PageGetSpecialPointer(p);
	so = (RTreeScanOpaque) s->opaque;

	/*
	 * If we modified the index during the scan, we may have a pointer to
	 * a ghost tuple, before the scan.	If this is the case, back up one.
	 */

	if (so->s_flags & RTS_CURBEFORE)
	{
		so->s_flags &= ~RTS_CURBEFORE;
		n = OffsetNumberPrev(n);
	}

	while (n >= FirstOffsetNumber && n <= maxoff)
	{
		it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
		if (po->flags & F_LEAF)
		{
			if (index_keytest(it,
							  RelationGetDescr(s->relation),
							  s->numberOfKeys, s->keyData))
				break;
		}
		else
		{
			if (index_keytest(it,
							  RelationGetDescr(s->relation),
							  so->s_internalNKey, so->s_internalKey))
				break;
		}

		if (ScanDirectionIsBackward(dir))
			n = OffsetNumberPrev(n);
		else
			n = OffsetNumberNext(n);
	}

	return n;
}

static RetrieveIndexResult
rtscancache(IndexScanDesc s, ScanDirection dir)
{
	RetrieveIndexResult res;
	ItemPointer ip;

	if (!(ScanDirectionIsNoMovement(dir)
		  && ItemPointerIsValid(&(s->currentItemData))))
	{

		return (RetrieveIndexResult) NULL;
	}

	ip = rtheapptr(s->relation, &(s->currentItemData));

	if (ItemPointerIsValid(ip))
		res = FormRetrieveIndexResult(&(s->currentItemData), ip);
	else
		res = (RetrieveIndexResult) NULL;

	pfree(ip);

	return res;
}

/*
 *	rtheapptr returns the item pointer to the tuple in the heap relation
 *	for which itemp is the index relation item pointer.
 */
static ItemPointer
rtheapptr(Relation r, ItemPointer itemp)
{
	Buffer		b;
	Page		p;
	IndexTuple	it;
	ItemPointer ip;
	OffsetNumber n;

	ip = (ItemPointer) palloc(sizeof(ItemPointerData));
	if (ItemPointerIsValid(itemp))
	{
		b = ReadBuffer(r, ItemPointerGetBlockNumber(itemp));
		p = BufferGetPage(b);
		n = ItemPointerGetOffsetNumber(itemp);
		it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
		memmove((char *) ip, (char *) &(it->t_tid),
				sizeof(ItemPointerData));
		ReleaseBuffer(b);
	}
	else
		ItemPointerSetInvalid(ip);

	return ip;
}
