/*------------------------------------------------------------------------- * * 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-2023, 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" #include "utils/memutils.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) { /* * 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) { 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); }