/*-------------------------------------------------------------------------
 *
 * execFlatten.c
 *	  This file handles the nodes associated with flattening sets in the
 *	  target list of queries containing functions returning sets.
 *
 * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /home/projects/pgsql/cvsroot/pgsql/src/backend/executor/execFlatten.c,v 1.12 2000/01/26 05:56:21 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */

/*
 * ExecEvalIter() -
 *	 Iterate through the all return tuples/base types from a function one
 *	 at time (i.e. one per ExecEvalIter call).	Not really needed for
 *	 postquel functions, but for reasons of orthogonality, these nodes
 *	 exist above pq functions as well as c functions.
 *
 * ExecEvalFjoin() -
 *	 Given N Iter nodes return a vector of all combinations of results
 *	 one at a time (i.e. one result vector per ExecEvalFjoin call).  This
 *	 node does the actual flattening work.
 */
#include "postgres.h"
#include "executor/execFlatten.h"
#include "executor/executor.h"

#ifdef SETS_FIXED
static bool FjoinBumpOuterNodes(TargetEntry *tlist, ExprContext *econtext,
					DatumPtr results, char *nulls);

#endif

Datum
ExecEvalIter(Iter *iterNode,
			 ExprContext *econtext,
			 bool *resultIsNull,
			 bool *iterIsDone)
{
	Node	   *expression;

	expression = iterNode->iterexpr;

	/*
	 * Really Iter nodes are only needed for C functions, postquel
	 * function by their nature return 1 result at a time.	For now we are
	 * only worrying about postquel functions, c functions will come
	 * later.
	 */
	return ExecEvalExpr(expression, econtext, resultIsNull, iterIsDone);
}

void
ExecEvalFjoin(TargetEntry *tlist,
			  ExprContext *econtext,
			  bool *isNullVect,
			  bool *fj_isDone)
{

#ifdef SETS_FIXED
	bool		isDone;
	int			curNode;
	List	   *tlistP;

	Fjoin	   *fjNode = tlist->fjoin;
	DatumPtr	resVect = fjNode->fj_results;
	BoolPtr		alwaysDone = fjNode->fj_alwaysDone;

	if (fj_isDone)
		*fj_isDone = false;

	/*
	 * For the next tuple produced by the plan, we need to re-initialize
	 * the Fjoin node.
	 */
	if (!fjNode->fj_initialized)
	{

		/*
		 * Initialize all of the Outer nodes
		 */
		curNode = 1;
		foreach(tlistP, lnext(tlist))
		{
			TargetEntry *tle = lfirst(tlistP);

			resVect[curNode] = ExecEvalIter((Iter *) tle->expr,
											econtext,
											&isNullVect[curNode],
											&isDone);
			if (isDone)
				isNullVect[curNode] = alwaysDone[curNode] = true;
			else
				alwaysDone[curNode] = false;

			curNode++;
		}

		/*
		 * Initialize the inner node
		 */
		resVect[0] = ExecEvalIter((Iter *) fjNode->fj_innerNode->expr,
								  econtext,
								  &isNullVect[0],
								  &isDone);
		if (isDone)
			isNullVect[0] = alwaysDone[0] = true;
		else
			alwaysDone[0] = false;

		/*
		 * Mark the Fjoin as initialized now.
		 */
		fjNode->fj_initialized = TRUE;

		/*
		 * If the inner node is always done, then we are done for now
		 */
		if (isDone)
			return;
	}
	else
	{

		/*
		 * If we're already initialized, all we need to do is get the next
		 * inner result and pair it up with the existing outer node result
		 * vector.	Watch out for the degenerate case, where the inner
		 * node never returns results.
		 */

		/*
		 * Fill in nulls for every function that is always done.
		 */
		for (curNode = fjNode->fj_nNodes - 1; curNode >= 0; curNode--)
			isNullVect[curNode] = alwaysDone[curNode];

		if (alwaysDone[0] == true)
		{
			*fj_isDone = FjoinBumpOuterNodes(tlist,
											 econtext,
											 resVect,
											 isNullVect);
			return;
		}
		else
			resVect[0] = ExecEvalIter((Iter *) fjNode->fj_innerNode->expr,
									  econtext,
									  &isNullVect[0],
									  &isDone);
	}

	/*
	 * if the inner node is done
	 */
	if (isDone)
	{
		*fj_isDone = FjoinBumpOuterNodes(tlist,
										 econtext,
										 resVect,
										 isNullVect);
		if (*fj_isDone)
			return;

		resVect[0] = ExecEvalIter((Iter *) fjNode->fj_innerNode->expr,
								  econtext,
								  &isNullVect[0],
								  &isDone);

	}
#endif
	return;
}

#ifdef SETS_FIXED
static bool
FjoinBumpOuterNodes(TargetEntry *tlist,
					ExprContext *econtext,
					DatumPtr results,
					char *nulls)
{
	bool		funcIsDone = true;
	Fjoin	   *fjNode = tlist->fjoin;
	char	   *alwaysDone = fjNode->fj_alwaysDone;
	List	   *outerList = lnext(tlist);
	List	   *trailers = lnext(tlist);
	int			trailNode = 1;
	int			curNode = 1;

	/*
	 * Run through list of functions until we get to one that isn't yet
	 * done returning values.  Watch out for funcs that are always done.
	 */
	while ((funcIsDone == true) && (outerList != NIL))
	{
		TargetEntry *tle = lfirst(outerList);

		if (alwaysDone[curNode] == true)
			nulls[curNode] = 'n';
		else
			results[curNode] = ExecEvalIter((Iter) tle->expr,
											econtext,
											&nulls[curNode],
											&funcIsDone);
		curNode++;
		outerList = lnext(outerList);
	}

	/*
	 * If every function is done, then we are done flattening. Mark the
	 * Fjoin node uninitialized, it is time to get the next tuple from the
	 * plan and redo all of the flattening.
	 */
	if (funcIsDone)
	{
		set_fj_initialized(fjNode, false);
		return true;
	}

	/*
	 * We found a function that wasn't done.  Now re-run every function
	 * before it.  As usual watch out for functions that are always done.
	 */
	trailNode = 1;
	while (trailNode != curNode - 1)
	{
		TargetEntry *tle = lfirst(trailers);

		if (alwaysDone[trailNode] != true)
			results[trailNode] = ExecEvalIter((Iter) tle->expr,
											  econtext,
											  &nulls[trailNode],
											  &funcIsDone);
		trailNode++;
		trailers = lnext(trailers);
	}
	return false;
}

#endif
