mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
Evaluation of set returning functions (SRFs_ in the targetlist (like SELECT generate_series(1,5)) so far was done in the expression evaluation (i.e. ExecEvalExpr()) and projection (i.e. ExecProject/ExecTargetList) code. This meant that most executor nodes performing projection, and most expression evaluation functions, had to deal with the possibility that an evaluated expression could return a set of return values. That's bad because it leads to repeated code in a lot of places. It also, and that's my (Andres's) motivation, made it a lot harder to implement a more efficient way of doing expression evaluation. To fix this, introduce a new executor node (ProjectSet) that can evaluate targetlists containing one or more SRFs. To avoid the complexity of the old way of handling nested expressions returning sets (e.g. having to pass up ExprDoneCond, and dealing with arguments to functions returning sets etc.), those SRFs can only be at the top level of the node's targetlist. The planner makes sure (via split_pathtarget_at_srfs()) that SRF evaluation is only necessary in ProjectSet nodes and that SRFs are only present at the top level of the node's targetlist. If there are nested SRFs the planner creates multiple stacked ProjectSet nodes. The ProjectSet nodes always get input from an underlying node. We also discussed and prototyped evaluating targetlist SRFs using ROWS FROM(), but that turned out to be more complicated than we'd hoped. While moving SRF evaluation to ProjectSet would allow to retain the old "least common multiple" behavior when multiple SRFs are present in one targetlist (i.e. continue returning rows until all SRFs are at the end of their input at the same time), we decided to instead only return rows till all SRFs are exhausted, returning NULL for already exhausted ones. We deemed the previous behavior to be too confusing, unexpected and actually not particularly useful. As a side effect, the previously prohibited case of multiple set returning arguments to a function, is now allowed. Not because it's particularly desirable, but because it ends up working and there seems to be no argument for adding code to prohibit it. Currently the behavior for COALESCE and CASE containing SRFs has changed, returning multiple rows from the expression, even when the SRF containing "arm" of the expression is not evaluated. That's because the SRFs are evaluated in a separate ProjectSet node. As that's quite confusing, we're likely to instead prohibit SRFs in those places. But that's still being discussed, and the code would reside in places not touched here, so that's a task for later. There's a lot of, now superfluous, code dealing with set return expressions around. But as the changes to get rid of those are verbose largely boring, it seems better for readability to keep the cleanup as a separate commit. Author: Tom Lane and Andres Freund Discussion: https://postgr.es/m/20160822214023.aaxz5l4igypowyri@alap3.anarazel.de
301 lines
7.4 KiB
C
301 lines
7.4 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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-2017, 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 "utils/memutils.h"
|
|
|
|
|
|
static TupleTableSlot *ExecProjectSRF(ProjectSetState *node, bool continuing);
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecProjectSet(node)
|
|
*
|
|
* Return tuples after evaluating the targetlist (which contains set
|
|
* returning functions).
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
TupleTableSlot *
|
|
ExecProjectSet(ProjectSetState *node)
|
|
{
|
|
TupleTableSlot *outerTupleSlot;
|
|
TupleTableSlot *resultSlot;
|
|
PlanState *outerPlan;
|
|
ExprContext *econtext;
|
|
|
|
econtext = node->ps.ps_ExprContext;
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* Reset per-tuple memory 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.
|
|
*/
|
|
ResetExprContext(econtext);
|
|
|
|
/*
|
|
* Get another input tuple and project SRFs from it.
|
|
*/
|
|
for (;;)
|
|
{
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
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;
|
|
bool hassrf PG_USED_FOR_ASSERTS_ONLY = false;
|
|
bool hasresult;
|
|
int argno;
|
|
ListCell *lc;
|
|
|
|
ExecClearTuple(resultSlot);
|
|
|
|
/*
|
|
* Assume no further tuples are produced unless an ExprMultipleResult is
|
|
* encountered from a set returning function.
|
|
*/
|
|
node->pending_srf_tuples = false;
|
|
|
|
hasresult = false;
|
|
argno = 0;
|
|
foreach(lc, node->ps.targetlist)
|
|
{
|
|
GenericExprState *gstate = (GenericExprState *) lfirst(lc);
|
|
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(gstate->arg, FuncExprState) &&
|
|
((FuncExprState *) gstate->arg)->funcReturnsSet)
|
|
{
|
|
/*
|
|
* Evaluate SRF - possibly continuing previously started output.
|
|
*/
|
|
*result = ExecMakeFunctionResultSet((FuncExprState *) gstate->arg,
|
|
econtext, 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(gstate->arg, econtext, isnull, NULL);
|
|
*isdone = ExprSingleResult;
|
|
}
|
|
|
|
argno++;
|
|
}
|
|
|
|
/* ProjectSet should not be used if there's no SRFs */
|
|
Assert(hassrf);
|
|
|
|
/*
|
|
* If all the SRFs returned EndResult, 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;
|
|
|
|
/* 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->pending_srf_tuples = false;
|
|
|
|
/*
|
|
* Miscellaneous initialization
|
|
*
|
|
* create expression context for node
|
|
*/
|
|
ExecAssignExprContext(estate, &state->ps);
|
|
|
|
/*
|
|
* tuple table initialization
|
|
*/
|
|
ExecInitResultTupleSlot(estate, &state->ps);
|
|
|
|
/*
|
|
* initialize child expressions
|
|
*/
|
|
state->ps.targetlist = (List *)
|
|
ExecInitExpr((Expr *) node->plan.targetlist,
|
|
(PlanState *) state);
|
|
Assert(node->plan.qual == NIL);
|
|
|
|
/*
|
|
* initialize child nodes
|
|
*/
|
|
outerPlanState(state) = ExecInitNode(outerPlan(node), estate, eflags);
|
|
|
|
/*
|
|
* we don't use inner plan
|
|
*/
|
|
Assert(innerPlan(node) == NULL);
|
|
|
|
/*
|
|
* initialize tuple type and projection info
|
|
*/
|
|
ExecAssignResultTypeFromTL(&state->ps);
|
|
|
|
/* Create workspace for per-SRF is-done state */
|
|
state->nelems = list_length(node->plan.targetlist);
|
|
state->elemdone = (ExprDoneCond *)
|
|
palloc(sizeof(ExprDoneCond) * state->nelems);
|
|
|
|
return state;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecEndProjectSet
|
|
*
|
|
* frees up storage allocated through C routines
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
void
|
|
ExecEndProjectSet(ProjectSetState *node)
|
|
{
|
|
/*
|
|
* Free the exprcontext
|
|
*/
|
|
ExecFreeExprContext(&node->ps);
|
|
|
|
/*
|
|
* clean out the tuple table
|
|
*/
|
|
ExecClearTuple(node->ps.ps_ResultTupleSlot);
|
|
|
|
/*
|
|
* shut down subplans
|
|
*/
|
|
ExecEndNode(outerPlanState(node));
|
|
}
|
|
|
|
void
|
|
ExecReScanProjectSet(ProjectSetState *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 (node->ps.lefttree->chgParam == NULL)
|
|
ExecReScan(node->ps.lefttree);
|
|
}
|