/*-------------------------------------------------------------------------
 *
 * nodeProjectSet.c
 *	  support for evaluating targetlists containing set-returning functions
 *
 * DESCRIPTION
 *
 *		ProjectSet nodes are inserted by the planner to evaluate set-returning
 *		functions in the targetlist.  It's guaranteed that all set-returning
 *		functions are directly at the top level of the targetlist, i.e. they
 *		can't be inside more-complex expressions.  If that'd otherwise be
 *		the case, the planner adds additional ProjectSet nodes.
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeProjectSet.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "executor/executor.h"
#include "executor/nodeProjectSet.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"


static TupleTableSlot *ExecProjectSRF(ProjectSetState *node, bool continuing);


/* ----------------------------------------------------------------
 *		ExecProjectSet(node)
 *
 *		Return tuples after evaluating the targetlist (which contains set
 *		returning functions).
 * ----------------------------------------------------------------
 */
static TupleTableSlot *
ExecProjectSet(PlanState *pstate)
{
	ProjectSetState *node = castNode(ProjectSetState, pstate);
	TupleTableSlot *outerTupleSlot;
	TupleTableSlot *resultSlot;
	PlanState  *outerPlan;
	ExprContext *econtext;

	CHECK_FOR_INTERRUPTS();

	econtext = node->ps.ps_ExprContext;

	/*
	 * Reset per-tuple context to free expression-evaluation storage allocated
	 * for a potentially previously returned tuple. Note that the SRF argument
	 * context has a different lifetime and is reset below.
	 */
	ResetExprContext(econtext);

	/*
	 * Check to see if we're still projecting out tuples from a previous scan
	 * tuple (because there is a function-returning-set in the projection
	 * expressions).  If so, try to project another one.
	 */
	if (node->pending_srf_tuples)
	{
		resultSlot = ExecProjectSRF(node, true);

		if (resultSlot != NULL)
			return resultSlot;
	}

	/*
	 * Get another input tuple and project SRFs from it.
	 */
	for (;;)
	{
		/*
		 * Reset argument context to free any expression evaluation storage
		 * allocated in the previous tuple cycle.  Note this can't happen
		 * until we're done projecting out tuples from a scan tuple, as
		 * ValuePerCall functions are allowed to reference the arguments for
		 * each returned tuple.  However, if we loop around after finding that
		 * no rows are produced from a scan tuple, we should reset, to avoid
		 * leaking memory when many successive scan tuples produce no rows.
		 */
		MemoryContextReset(node->argcontext);

		/*
		 * Retrieve tuples from the outer plan until there are no more.
		 */
		outerPlan = outerPlanState(node);
		outerTupleSlot = ExecProcNode(outerPlan);

		if (TupIsNull(outerTupleSlot))
			return NULL;

		/*
		 * Prepare to compute projection expressions, which will expect to
		 * access the input tuples as varno OUTER.
		 */
		econtext->ecxt_outertuple = outerTupleSlot;

		/* Evaluate the expressions */
		resultSlot = ExecProjectSRF(node, false);

		/*
		 * Return the tuple unless the projection produced no rows (due to an
		 * empty set), in which case we must loop back to see if there are
		 * more outerPlan tuples.
		 */
		if (resultSlot)
			return resultSlot;

		/*
		 * When we do loop back, we'd better reset the econtext again, just in
		 * case the SRF leaked some memory there.
		 */
		ResetExprContext(econtext);
	}

	return NULL;
}

/* ----------------------------------------------------------------
 *		ExecProjectSRF
 *
 *		Project a targetlist containing one or more set-returning functions.
 *
 *		'continuing' indicates whether to continue projecting rows for the
 *		same input tuple; or whether a new input tuple is being projected.
 *
 *		Returns NULL if no output tuple has been produced.
 *
 * ----------------------------------------------------------------
 */
static TupleTableSlot *
ExecProjectSRF(ProjectSetState *node, bool continuing)
{
	TupleTableSlot *resultSlot = node->ps.ps_ResultTupleSlot;
	ExprContext *econtext = node->ps.ps_ExprContext;
	MemoryContext oldcontext;
	bool		hassrf PG_USED_FOR_ASSERTS_ONLY;
	bool		hasresult;
	int			argno;

	ExecClearTuple(resultSlot);

	/* Call SRFs, as well as plain expressions, in per-tuple context */
	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);

	/*
	 * Assume no further tuples are produced unless an ExprMultipleResult is
	 * encountered from a set returning function.
	 */
	node->pending_srf_tuples = false;

	hassrf = hasresult = false;
	for (argno = 0; argno < node->nelems; argno++)
	{
		Node	   *elem = node->elems[argno];
		ExprDoneCond *isdone = &node->elemdone[argno];
		Datum	   *result = &resultSlot->tts_values[argno];
		bool	   *isnull = &resultSlot->tts_isnull[argno];

		if (continuing && *isdone == ExprEndResult)
		{
			/*
			 * If we're continuing to project output rows from a source tuple,
			 * return NULLs once the SRF has been exhausted.
			 */
			*result = (Datum) 0;
			*isnull = true;
			hassrf = true;
		}
		else if (IsA(elem, SetExprState))
		{
			/*
			 * Evaluate SRF - possibly continuing previously started output.
			 */
			*result = ExecMakeFunctionResultSet((SetExprState *) elem,
												econtext, node->argcontext,
												isnull, isdone);

			if (*isdone != ExprEndResult)
				hasresult = true;
			if (*isdone == ExprMultipleResult)
				node->pending_srf_tuples = true;
			hassrf = true;
		}
		else
		{
			/* Non-SRF tlist expression, just evaluate normally. */
			*result = ExecEvalExpr((ExprState *) elem, econtext, isnull);
			*isdone = ExprSingleResult;
		}
	}

	MemoryContextSwitchTo(oldcontext);

	/* ProjectSet should not be used if there's no SRFs */
	Assert(hassrf);

	/*
	 * If all the SRFs returned ExprEndResult, we consider that as no row
	 * being produced.
	 */
	if (hasresult)
	{
		ExecStoreVirtualTuple(resultSlot);
		return resultSlot;
	}

	return NULL;
}

/* ----------------------------------------------------------------
 *		ExecInitProjectSet
 *
 *		Creates the run-time state information for the ProjectSet node
 *		produced by the planner and initializes outer relations
 *		(child nodes).
 * ----------------------------------------------------------------
 */
ProjectSetState *
ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
{
	ProjectSetState *state;
	ListCell   *lc;
	int			off;

	/* check for unsupported flags */
	Assert(!(eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD)));

	/*
	 * create state structure
	 */
	state = makeNode(ProjectSetState);
	state->ps.plan = (Plan *) node;
	state->ps.state = estate;
	state->ps.ExecProcNode = ExecProjectSet;

	state->pending_srf_tuples = false;

	/*
	 * Miscellaneous initialization
	 *
	 * create expression context for node
	 */
	ExecAssignExprContext(estate, &state->ps);

	/*
	 * initialize child nodes
	 */
	outerPlanState(state) = ExecInitNode(outerPlan(node), estate, eflags);

	/*
	 * we don't use inner plan
	 */
	Assert(innerPlan(node) == NULL);

	/*
	 * tuple table and result type initialization
	 */
	ExecInitResultTupleSlotTL(&state->ps, &TTSOpsVirtual);

	/* Create workspace for per-tlist-entry expr state & SRF-is-done state */
	state->nelems = list_length(node->plan.targetlist);
	state->elems = (Node **)
		palloc(sizeof(Node *) * state->nelems);
	state->elemdone = (ExprDoneCond *)
		palloc(sizeof(ExprDoneCond) * state->nelems);

	/*
	 * Build expressions to evaluate targetlist.  We can't use
	 * ExecBuildProjectionInfo here, since that doesn't deal with SRFs.
	 * Instead compile each expression separately, using
	 * ExecInitFunctionResultSet where applicable.
	 */
	off = 0;
	foreach(lc, node->plan.targetlist)
	{
		TargetEntry *te = (TargetEntry *) lfirst(lc);
		Expr	   *expr = te->expr;

		if ((IsA(expr, FuncExpr) && ((FuncExpr *) expr)->funcretset) ||
			(IsA(expr, OpExpr) && ((OpExpr *) expr)->opretset))
		{
			state->elems[off] = (Node *)
				ExecInitFunctionResultSet(expr, state->ps.ps_ExprContext,
										  &state->ps);
		}
		else
		{
			Assert(!expression_returns_set((Node *) expr));
			state->elems[off] = (Node *) ExecInitExpr(expr, &state->ps);
		}

		off++;
	}

	/* We don't support any qual on ProjectSet nodes */
	Assert(node->plan.qual == NIL);

	/*
	 * Create a memory context that ExecMakeFunctionResultSet can use to
	 * evaluate function arguments in.  We can't use the per-tuple context for
	 * this because it gets reset too often; but we don't want to leak
	 * evaluation results into the query-lifespan context either.  We use one
	 * context for the arguments of all tSRFs, as they have roughly equivalent
	 * lifetimes.
	 */
	state->argcontext = AllocSetContextCreate(CurrentMemoryContext,
											  "tSRF function arguments",
											  ALLOCSET_DEFAULT_SIZES);

	return state;
}

/* ----------------------------------------------------------------
 *		ExecEndProjectSet
 *
 *		frees up storage allocated through C routines
 * ----------------------------------------------------------------
 */
void
ExecEndProjectSet(ProjectSetState *node)
{
	/*
	 * shut down subplans
	 */
	ExecEndNode(outerPlanState(node));
}

void
ExecReScanProjectSet(ProjectSetState *node)
{
	PlanState  *outerPlan = outerPlanState(node);

	/* Forget any incompletely-evaluated SRFs */
	node->pending_srf_tuples = false;

	/*
	 * If chgParam of subnode is not null then plan will be re-scanned by
	 * first ExecProcNode.
	 */
	if (outerPlan->chgParam == NULL)
		ExecReScan(outerPlan);
}
