/*-------------------------------------------------------------------------
 *
 * nodeTee.c
 *
 *
 * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	 DESCRIPTION
 *		This code provides support for a tee node, which allows
 *		multiple parent in a megaplan.
 *
 *	 INTERFACE ROUTINES
 *		ExecTee
 *		ExecInitTee
 *		ExecEndTee
 *
 *	$Id: nodeTee.c,v 1.12 2002/06/20 20:29:28 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */

#include <sys/types.h>
#include <sys/file.h>
#include "postgres.h"

#include "access/heapam.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "executor/executor.h"
#include "executor/nodeTee.h"
#include "optimizer/internal.h"
#include "storage/bufmgr.h"
#include "storage/smgr.h"
#include "tcop/pquery.h"
#include "utils/relcache.h"

/* ------------------------------------------------------------------
 *		ExecInitTee
 *
 *		Create tee state
 *
 * ------------------------------------------------------------------
 */
bool
ExecInitTee(Tee * node, EState *currentEstate, Plan *parent)
{
	TeeState   *teeState;
	Plan	   *outerPlan;
	int			len;
	Relation	bufferRel;
	TupleDesc	tupType;
	EState	   *estate;

	/*
	 * it is possible that the Tee has already been initialized since it
	 * can be reached by multiple parents. If it is already initialized,
	 * simply return and do not initialize the children nodes again
	 */
	if (node->plan.state)
		return TRUE;

	/*
	 * assign the node's execution state
	 */

	/*
	 * make a new executor state, because we have a different
	 * es_range_table
	 */

/*	   node->plan.state = estate;*/

	estate = CreateExecutorState();
	estate->es_direction = currentEstate->es_direction;
	estate->es_BaseId = currentEstate->es_BaseId;
	estate->es_BaseId = currentEstate->es_BaseId;
	estate->es_tupleTable = currentEstate->es_tupleTable;
	estate->es_refcount = currentEstate->es_refcount;
	estate->es_junkFilter = currentEstate->es_junkFilter;
	estate->es_snapshot = currentEstate->es_snapshot;

	/*
	 * use the range table for Tee subplan since the range tables for the
	 * two parents may be different
	 */
	if (node->rtentries)
		estate->es_range_table = node->rtentries;
	else
		estate->es_range_table = currentEstate->es_range_table;

	node->plan.state = estate;


	/*
	 * create teeState structure
	 */
	teeState = makeNode(TeeState);
	teeState->tee_leftPlace = 0;
	teeState->tee_rightPlace = 0;
	teeState->tee_lastPlace = 0;
	teeState->tee_bufferRel = NULL;
	teeState->tee_leftScanDesc = NULL;
	teeState->tee_rightScanDesc = NULL;


	node->teestate = teeState;

	/* ----------------
	 *	Miscellanious initialization
	 *
	 *		 +	assign node's base_id
	 *		 +	assign debugging hooks and
	 *		 +	create expression context for node
	 * ----------------
	 */
	ExecAssignNodeBaseInfo(estate, &(teeState->cstate), parent);
	ExecAssignExprContext(estate, &(teeState->cstate));

#define TEE_NSLOTS 2

	/*
	 * initialize tuple slots
	 */
	ExecInitResultTupleSlot(estate, &(teeState->cstate));

	/* initialize child nodes */
	outerPlan = outerPlan((Plan *) node);
	ExecInitNode(outerPlan, estate, (Plan *) node);

	/*
	 * the tuple type info is from the outer plan of this node the result
	 * type is also the same as the outerplan
	 */
	ExecAssignResultTypeFromOuterPlan((Plan *) node, &(teeState->cstate));
	ExecAssignProjectionInfo((Plan *) node, &teeState->cstate);

	/*
	 * initialize temporary relation to buffer tuples
	 */
	tupType = ExecGetResultType(&(teeState->cstate));
	len = ExecTargetListLength(((Plan *) node)->targetlist);

	/*
	 * create a catalogued relation even though this is a temporary
	 * relation
	 */
	/* cleanup of catalogued relations is easier to do */

	if (node->teeTableName[0] != '\0')
	{
		Relation	r;

		teeState->tee_bufferRelname = pstrdup(node->teeTableName);

		/*
		 * we are given an tee table name, if a relation by that name
		 * exists, then we open it, else we create it and then open it
		 */
		r = RelationNameGetRelation(teeState->tee_bufferRelname);

		if (RelationIsValid(r))
			bufferRel = heap_openr(teeState->tee_bufferRelname);
		else
			bufferRel = heap_open(
					heap_create_with_catalog(teeState->tee_bufferRelname,
									  tupType, RELKIND_RELATION, false));
	}
	else
	{
		sprintf(teeState->tee_bufferRelname,
				"ttemp_%d",		/* 'ttemp' for 'tee' temporary */
				newoid());
		bufferRel = heap_open(
					heap_create_with_catalog(teeState->tee_bufferRelname,
									  tupType, RELKIND_RELATION, false));
	}

	teeState->tee_bufferRel = bufferRel;

	/*
	 * initialize a memory context for allocating thing like scan
	 * descriptors
	 */

	/*
	 * we do this so that on cleanup of the tee, we can free things. if we
	 * didn't have our own memory context, we would be in the memory
	 * context of the portal that we happen to be using at the moment
	 */

	teeState->tee_mcxt = (MemoryContext) CreateGlobalMemory(teeState->tee_bufferRelname);

	/*
	 * don't initialize the scan descriptors here because it's not good to
	 * initialize scan descriptors on empty rels. Wait until the scan
	 * descriptors are needed before initializing them.
	 */

	teeState->tee_leftScanDesc = NULL;
	teeState->tee_rightScanDesc = NULL;

	return TRUE;
}

int
ExecCountSlotsTee(Tee * node)
{
	/* Tee nodes can't have innerPlans */
	return ExecCountSlotsNode(outerPlan(node)) + TEE_NSLOTS;
}

/* ----------------------------------------------------------------
   initTeeScanDescs
	  initializes the left and right scandescs on the temporary
	  relation of a Tee node

	  must open two separate scan descriptors,
	  because the left and right scans may be at different points
* ----------------------------------------------------------------
*/
static void
initTeeScanDescs(Tee * node)
{
	TeeState   *teeState;
	Relation	bufferRel;
	ScanDirection dir;
	Snapshot	snapshot;
	MemoryContext orig;

	teeState = node->teestate;
	if (teeState->tee_leftScanDesc && teeState->tee_rightScanDesc)
		return;

	orig = CurrentMemoryContext;
	MemoryContextSwitchTo(teeState->tee_mcxt);

	bufferRel = teeState->tee_bufferRel;
	dir = ((Plan *) node)->state->es_direction; /* backwards not handled
												 * yet XXX */
	snapshot = ((Plan *) node)->state->es_snapshot;

	if (teeState->tee_leftScanDesc == NULL)
	{
		teeState->tee_leftScanDesc = heap_beginscan(bufferRel,
											ScanDirectionIsBackward(dir),
													snapshot,
													0,	/* num scan keys */
													NULL		/* scan keys */
			);
	}
	if (teeState->tee_rightScanDesc == NULL)
	{
		teeState->tee_rightScanDesc = heap_beginscan(bufferRel,
											ScanDirectionIsBackward(dir),
													 snapshot,
													 0, /* num scan keys */
													 NULL		/* scan keys */
			);
	}

	MemoryContextSwitchTo(orig);
}

/* ----------------------------------------------------------------
 *		ExecTee(node)
 *
 *
 *		A Tee serves to connect a subplan to multiple parents.
 *		the subplan is always the outplan of the Tee node.
 *
 *		The Tee gets requests from either leftParent or rightParent,
 *		fetches the result tuple from the child, and then
 *		stored the result into a temporary relation (serving as a queue).
 *		leftPlace and rightPlace keep track of where the left and rightParents
 *		are.
 *		If a parent requests a tuple and that parent is not at the end
 *		of the temporary relation, then the request is satisfied from
 *		the queue instead of by executing the child plan
 *
 * ----------------------------------------------------------------
 */

TupleTableSlot *
ExecTee(Tee * node, Plan *parent)
{
	EState	   *estate;
	TeeState   *teeState;
	int			leftPlace,
				rightPlace,
				lastPlace;
	int			branch;
	TupleTableSlot *result;
	TupleTableSlot *slot;
	Plan	   *childNode;
	ScanDirection dir;
	HeapTuple	heapTuple;
	Relation	bufferRel;
	HeapScanDesc scanDesc;

	estate = ((Plan *) node)->state;
	teeState = node->teestate;
	leftPlace = teeState->tee_leftPlace;
	rightPlace = teeState->tee_rightPlace;
	lastPlace = teeState->tee_lastPlace;
	bufferRel = teeState->tee_bufferRel;

	childNode = outerPlan(node);

	dir = estate->es_direction;

	/* XXX doesn't handle backwards direction yet */

	if (parent == node->leftParent)
		branch = leftPlace;
	else if ((parent == node->rightParent) || (parent == (Plan *) node))

		/*
		 * the tee node could be the root node of the plan, in which case,
		 * we treat it like a right-parent pull
		 */
		branch = rightPlace;
	else
	{
		elog(ERROR, "A Tee node can only be executed from its left or right parent\n");
		return NULL;
	}

	if (branch == lastPlace)
	{							/* we're at the end of the queue already,
								 * - get a new tuple from the child plan,
								 * - store it in the queue, - increment
								 * lastPlace, - increment leftPlace or
								 * rightPlace as appropriate, - and return
								 * result */
		slot = ExecProcNode(childNode, (Plan *) node);
		if (!TupIsNull(slot))
		{
			/*
			 * heap_insert changes something...
			 */
			if (slot->ttc_buffer != InvalidBuffer)
				heapTuple = heap_copytuple(slot->val);
			else
				heapTuple = slot->val;

			/* insert into temporary relation */
			heap_insert(bufferRel, heapTuple);

			if (slot->ttc_buffer != InvalidBuffer)
				heap_freetuple(heapTuple);

			/*
			 * once there is data in the temporary relation, ensure that
			 * the left and right scandescs are initialized
			 */
			initTeeScanDescs(node);

			scanDesc = (parent == node->leftParent) ?
				teeState->tee_leftScanDesc : teeState->tee_rightScanDesc;

			{
				/*
				 * move the scandesc forward so we don't re-read this
				 * tuple later
				 */
				HeapTuple	throwAway;

				/* Buffer buffer; */
				throwAway = heap_getnext(scanDesc, ScanDirectionIsBackward(dir));
			}

			/*
			 * set the shouldFree field of the child's slot so that when
			 * the child's slot is free'd, this tuple isn't free'd also
			 */

			/*
			 * does this mean this tuple has to be garbage collected
			 * later??
			 */
			slot->ttc_shouldFree = false;

			teeState->tee_lastPlace = lastPlace + 1;
		}
		result = slot;
	}
	else
	{							/* the desired data already exists in the
								 * temporary relation */
		scanDesc = (parent == node->leftParent) ?
			teeState->tee_leftScanDesc : teeState->tee_rightScanDesc;

		heapTuple = heap_getnext(scanDesc, ScanDirectionIsBackward(dir));

		/*
		 * Increase the pin count on the buffer page, because the tuple
		 * stored in the slot also points to it (as well as the scan
		 * descriptor). If we don't, ExecStoreTuple will decrease the pin
		 * count on the next iteration.
		 */

		if (scanDesc->rs_cbuf != InvalidBuffer)
			IncrBufferRefCount(scanDesc->rs_cbuf);

		slot = teeState->cstate.cs_ResultTupleSlot;
		slot->ttc_tupleDescriptor = RelationGetDescr(bufferRel);

		result = ExecStoreTuple(heapTuple,		/* tuple to store */
								slot,	/* slot to store in */
								scanDesc->rs_cbuf,		/* this tuple's buffer */
								false); /* don't free stuff from
										 * heap_getnext */

	}

	if (parent == node->leftParent)
		teeState->tee_leftPlace = leftPlace + 1;
	else
		teeState->tee_rightPlace = rightPlace + 1;

	return result;
}

/* ---------------------------------------------------------------------
 *		ExecEndTee
 *
 *	 End the Tee node, and free up any storage
 * since a Tee node can be downstream of multiple parent nodes,
 * only free when both parents are done
 * --------------------------------------------------------------------
 */

void
ExecEndTee(Tee * node, Plan *parent)
{
	EState	   *estate;
	TeeState   *teeState;
	int			leftPlace,
				rightPlace,
				lastPlace;
	Relation	bufferRel;
	MemoryContext orig;

	estate = ((Plan *) node)->state;
	teeState = node->teestate;
	leftPlace = teeState->tee_leftPlace;
	rightPlace = teeState->tee_rightPlace;
	lastPlace = teeState->tee_lastPlace;

	if (!node->leftParent || parent == node->leftParent)
		leftPlace = -1;

	if (!node->rightParent || parent == node->rightParent)
		rightPlace = -1;

	if (parent == (Plan *) node)
		rightPlace = leftPlace = -1;

	teeState->tee_leftPlace = leftPlace;
	teeState->tee_rightPlace = rightPlace;
	if ((leftPlace == -1) && (rightPlace == -1))
	{
		/* remove the temporary relations */
		/* and close the scan descriptors */

		bufferRel = teeState->tee_bufferRel;
		if (bufferRel)
		{
			heap_drop(bufferRel);
			teeState->tee_bufferRel = NULL;
			if (teeState->tee_mcxt)
			{
				orig = CurrentMemoryContext;
				MemoryContextSwitchTo(teeState->tee_mcxt);
			}
			else
				orig = 0;

			if (teeState->tee_leftScanDesc)
			{
				heap_endscan(teeState->tee_leftScanDesc);
				teeState->tee_leftScanDesc = NULL;
			}
			if (teeState->tee_rightScanDesc)
			{
				heap_endscan(teeState->tee_rightScanDesc);
				teeState->tee_rightScanDesc = NULL;
			}

			if (teeState->tee_mcxt)
			{
				MemoryContextSwitchTo(orig);
				teeState->tee_mcxt = NULL;
			}
		}
	}

}
