From 1dabd48d3551a4d927c4df162ef1c53ba6b9d2b5 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Mon, 16 Jan 2017 12:40:13 -0800 Subject: [PATCH 2/3] Implement targetlist set returning functions in a new pipeline node. --- src/backend/commands/explain.c | 5 + src/backend/executor/Makefile | 4 +- src/backend/executor/execAmi.c | 5 + src/backend/executor/execProcnode.c | 14 ++ src/backend/executor/execQual.c | 112 +++++------ src/backend/executor/nodeSetResult.c | 316 +++++++++++++++++++++++++++++++ src/backend/nodes/copyfuncs.c | 19 ++ src/backend/nodes/outfuncs.c | 12 +- src/backend/nodes/readfuncs.c | 16 ++ src/backend/optimizer/path/allpaths.c | 3 + src/backend/optimizer/plan/createplan.c | 93 ++++++--- src/backend/optimizer/plan/planner.c | 4 +- src/backend/optimizer/plan/setrefs.c | 21 ++ src/backend/optimizer/plan/subselect.c | 1 + src/backend/optimizer/util/pathnode.c | 17 +- src/backend/optimizer/util/tlist.c | 2 +- src/include/executor/executor.h | 4 + src/include/executor/nodeSetResult.h | 24 +++ src/include/nodes/execnodes.h | 15 ++ src/include/nodes/nodes.h | 3 + src/include/nodes/plannodes.h | 7 + src/include/nodes/relation.h | 11 +- src/include/optimizer/pathnode.h | 2 +- src/test/regress/expected/aggregates.out | 2 +- src/test/regress/expected/limit.out | 8 +- src/test/regress/expected/portals.out | 8 +- src/test/regress/expected/subselect.out | 13 +- src/test/regress/expected/tsrf.out | 8 +- src/test/regress/expected/union.out | 2 +- 29 files changed, 634 insertions(+), 117 deletions(-) create mode 100644 src/backend/executor/nodeSetResult.c create mode 100644 src/include/executor/nodeSetResult.h diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index ee7046c47b..a1a42f747d 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -852,6 +852,11 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_Result: pname = sname = "Result"; break; + + case T_SetResult: + pname = sname = "SetResult"; + break; + case T_ModifyTable: sname = "ModifyTable"; switch (((ModifyTable *) plan)->operation) diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 51edd4c5e7..15587435d7 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -22,8 +22,8 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \ nodeLimit.o nodeLockRows.o \ nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \ nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \ - nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \ - nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \ + nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSetResult.o nodeSort.o \ + nodeUnique.o nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \ nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \ nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 3ea36979b3..c9c222f446 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -44,6 +44,7 @@ #include "executor/nodeSamplescan.h" #include "executor/nodeSeqscan.h" #include "executor/nodeSetOp.h" +#include "executor/nodeSetResult.h" #include "executor/nodeSort.h" #include "executor/nodeSubplan.h" #include "executor/nodeSubqueryscan.h" @@ -130,6 +131,10 @@ ExecReScan(PlanState *node) ExecReScanResult((ResultState *) node); break; + case T_SetResultState: + ExecReScanSetResult((SetResultState *) node); + break; + case T_ModifyTableState: ExecReScanModifyTable((ModifyTableState *) node); break; diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index b8edd36470..f3cc706f13 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -106,6 +106,7 @@ #include "executor/nodeSamplescan.h" #include "executor/nodeSeqscan.h" #include "executor/nodeSetOp.h" +#include "executor/nodeSetResult.h" #include "executor/nodeSort.h" #include "executor/nodeSubplan.h" #include "executor/nodeSubqueryscan.h" @@ -155,6 +156,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; + case T_SetResult: + result = (PlanState *) ExecInitSetResult((SetResult *) node, + estate, eflags); + break; + case T_ModifyTable: result = (PlanState *) ExecInitModifyTable((ModifyTable *) node, estate, eflags); @@ -392,6 +398,10 @@ ExecProcNode(PlanState *node) result = ExecResult((ResultState *) node); break; + case T_SetResultState: + result = ExecSetResult((SetResultState *) node); + break; + case T_ModifyTableState: result = ExecModifyTable((ModifyTableState *) node); break; @@ -634,6 +644,10 @@ ExecEndNode(PlanState *node) ExecEndResult((ResultState *) node); break; + case T_SetResultState: + ExecEndSetResult((SetResultState *) node); + break; + case T_ModifyTableState: ExecEndModifyTable((ModifyTableState *) node); break; diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index bf007b7efd..ad673ed8b7 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -29,9 +29,9 @@ * instead of doing needless copying. -cim 5/31/91 * * During expression evaluation, we check_stack_depth only in - * ExecMakeFunctionResult (and substitute routines) rather than at every - * single node. This is a compromise that trades off precision of the - * stack limit setting to gain speed. + * ExecMakeFunctionResultSet/ExecMakeFunctionResultNoSetrather than at + * every single node. This is a compromise that trades off precision of + * the stack limit setting to gain speed. */ #include "postgres.h" @@ -104,10 +104,6 @@ static void ExecPrepareTuplestoreResult(FuncExprState *fcache, Tuplestorestate *resultStore, TupleDesc resultDesc); static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc); -static Datum ExecMakeFunctionResult(FuncExprState *fcache, - ExprContext *econtext, - bool *isNull, - ExprDoneCond *isDone); static Datum ExecMakeFunctionResultNoSets(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -1549,7 +1545,7 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo, /* * ExecPrepareTuplestoreResult * - * Subroutine for ExecMakeFunctionResult: prepare to extract rows from a + * Subroutine for ExecMakeFunctionResultSet: prepare to extract rows from a * tuplestore function result. We must set up a funcResultSlot (unless * already done in a previous call cycle) and verify that the function * returned the expected tuple descriptor. @@ -1673,19 +1669,17 @@ tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc) } /* - * ExecMakeFunctionResult + * ExecMakeFunctionResultSet * - * Evaluate the arguments to a function and then the function itself. - * init_fcache is presumed already run on the FuncExprState. - * - * This function handles the most general case, wherein the function or - * one of its arguments can return a set. + * Evaluate the arguments to a set returning function and then call the + * function itself. The arguments themselves may not contain set returning + * functions (the planner is supposed to have separated evaluation for those). */ -static Datum -ExecMakeFunctionResult(FuncExprState *fcache, - ExprContext *econtext, - bool *isNull, - ExprDoneCond *isDone) +Datum +ExecMakeFunctionResultSet(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) { List *arguments; Datum result; @@ -1702,6 +1696,32 @@ restart: check_stack_depth(); /* + * Initialize function cache if first time through. Unfortunately the + * parent can be either an FuncExpr or OpExpr. This is a bit ugly. + */ + if (fcache->func.fn_oid == InvalidOid) + { + if (IsA(fcache->xprstate.expr, FuncExpr)) + { + FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; + + init_fcache(func->funcid, func->inputcollid, fcache, + econtext->ecxt_per_query_memory, true); + } + else if (IsA(fcache->xprstate.expr, OpExpr)) + { + OpExpr *op = (OpExpr *) fcache->xprstate.expr; + + init_fcache(op->opfuncid, op->inputcollid, fcache, + econtext->ecxt_per_query_memory, true); + } + else + { + elog(ERROR, "unexpected type"); + } + } + + /* * If a previous call of the function returned a set result in the form of * a tuplestore, continue reading rows from the tuplestore until it's * empty. @@ -2120,7 +2140,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, ExprDoneCond argDone; /* - * This path is similar to ExecMakeFunctionResult. + * This path is similar to ExecMakeFunctionResultSet. */ direct_function_call = true; @@ -2423,24 +2443,16 @@ ExecEvalFunc(FuncExprState *fcache, /* Initialize function lookup info */ init_fcache(func->funcid, func->inputcollid, fcache, - econtext->ecxt_per_query_memory, true); + econtext->ecxt_per_query_memory, false); - /* - * We need to invoke ExecMakeFunctionResult if either the function itself - * or any of its input expressions can return a set. Otherwise, invoke - * ExecMakeFunctionResultNoSets. In either case, change the evalfunc - * pointer to go directly there on subsequent uses. - */ - if (fcache->func.fn_retset || expression_returns_set((Node *) func->args)) - { - fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; - return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); - } - else - { - fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets; - return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone); - } + if (fcache->func.fn_retset) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + /* Change the evalfunc pointer, to skip the above initialization. */ + fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets; + return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone); } /* ---------------------------------------------------------------- @@ -2458,24 +2470,16 @@ ExecEvalOper(FuncExprState *fcache, /* Initialize function lookup info */ init_fcache(op->opfuncid, op->inputcollid, fcache, - econtext->ecxt_per_query_memory, true); + econtext->ecxt_per_query_memory, false); - /* - * We need to invoke ExecMakeFunctionResult if either the function itself - * or any of its input expressions can return a set. Otherwise, invoke - * ExecMakeFunctionResultNoSets. In either case, change the evalfunc - * pointer to go directly there on subsequent uses. - */ - if (fcache->func.fn_retset || expression_returns_set((Node *) op->args)) - { - fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; - return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); - } - else - { - fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets; - return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone); - } + if (fcache->func.fn_retset) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued operator called in context that cannot accept a set"))); + + /* Change the evalfunc pointer, to skip the above initialization. */ + fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets; + return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone); } /* ---------------------------------------------------------------- diff --git a/src/backend/executor/nodeSetResult.c b/src/backend/executor/nodeSetResult.c new file mode 100644 index 0000000000..6d9d96dca9 --- /dev/null +++ b/src/backend/executor/nodeSetResult.c @@ -0,0 +1,316 @@ +/*------------------------------------------------------------------------- + * + * nodeSetResult.c + * support for evaluating targetlists containing set returning functions + * + * DESCRIPTION + * + * SetResult 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. there + * can't be inside a more complex expressions. If that'd otherwise be + * the case, the planner adds additional SetResult nodes. + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/executor/nodeSetResult.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "executor/executor.h" +#include "executor/nodeSetResult.h" +#include "utils/memutils.h" + + +static TupleTableSlot * +ExecProjectSRF(SetResultState *node, bool continuing); + + +/* ---------------------------------------------------------------- + * ExecSetResult(node) + * + * Return tuples after evaluating the targetlist (which contains set + * returning functions). + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecSetResult(SetResultState *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); + + /* + * If input_done is true then it means that we were asked to return a + * constant tuple and we already did the last time ExecSetResult() was + * called. Either way, now we are through. + */ + while (!node->input_done) + { + outerPlan = outerPlanState(node); + + if (outerPlan != NULL) + { + /* + * Retrieve tuples from the outer plan until there are no more. + */ + 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; + } + else + { + /* + * If we don't have an outer plan, then we are just generating the + * results from a constant target list. Do it only once. + */ + node->input_done = true; + } + + resultSlot = ExecProjectSRF(node, false); + + /* + * Return the tuple unless the projection produced now 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 continuing 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(SetResultState *node, bool continuing) +{ + TupleTableSlot *resultSlot = node->ps.ps_ResultTupleSlot; + ExprContext *econtext = node->ps.ps_ExprContext; + ListCell *lc; + int argno; + bool hasresult; + bool hassrf = false PG_USED_FOR_ASSERTS_ONLY; + + ExecClearTuple(resultSlot); + + /* + * Assume no further tuples are produces 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 = 0; + *isnull = true; + hassrf = true; + } + else if (IsA(gstate->arg, FuncExprState) && + ((FuncExpr *) gstate->arg->expr)->funcretset) + { + /* + * Evaluate SRF - possibly continuing previously started output. + */ + *result = ExecMakeFunctionResultSet((FuncExprState *) gstate->arg, + econtext, isnull, isdone); + + if (node->elemdone[argno] != ExprEndResult) + hasresult = true; + if (node->elemdone[argno] == ExprMultipleResult) + node->pending_srf_tuples = true; + hassrf = true; + } + else + { + *result = ExecEvalExpr(gstate->arg, econtext, isnull, NULL); + *isdone = ExprSingleResult; + } + + argno++; + } + + /* SetResult should not be used if there's no SRFs */ + Assert(hassrf); + + /* + * If all the SRFs returned EndResult, we consider that as no result being + * produced. + */ + if (hasresult) + { + ExecStoreVirtualTuple(resultSlot); + return resultSlot; + } + + return NULL; +} + +/* ---------------------------------------------------------------- + * ExecInitResult + * + * Creates the run-time state information for the SetResult node + * produced by the planner and initializes outer relations + * (child nodes). + * ---------------------------------------------------------------- + */ +SetResultState * +ExecInitSetResult(SetResult *node, EState *estate, int eflags) +{ + SetResultState *state; + + /* check for unsupported flags */ + Assert(!(eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD)) || + outerPlan(node) != NULL); + + /* + * create state structure + */ + state = makeNode(SetResultState); + state->ps.plan = (Plan *) node; + state->ps.state = estate; + + state->input_done = false; + 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); + state->ps.qual = (List *) + ExecInitExpr((Expr *) node->plan.qual, + (PlanState *) state); + + /* + * 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); + + state->nelems = list_length(node->plan.targetlist); + state->elemdone = palloc(sizeof(ExprDoneCond) * state->nelems); + + return state; +} + +/* ---------------------------------------------------------------- + * ExecEndSetResult + * + * frees up storage allocated through C routines + * ---------------------------------------------------------------- + */ +void +ExecEndSetResult(SetResultState *node) +{ + /* + * Free the exprcontext + */ + ExecFreeExprContext(&node->ps); + + /* + * clean out the tuple table + */ + ExecClearTuple(node->ps.ps_ResultTupleSlot); + + /* + * shut down subplans + */ + ExecEndNode(outerPlanState(node)); +} + +void +ExecReScanSetResult(SetResultState *node) +{ + node->input_done = false; + 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 && + node->ps.lefttree->chgParam == NULL) + ExecReScan(node->ps.lefttree); +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 7107bbf164..37fbb35455 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -166,6 +166,22 @@ _copyResult(const Result *from) } /* + * _copySetResult + */ +static SetResult * +_copySetResult(const SetResult *from) +{ + SetResult *newnode = makeNode(SetResult); + + /* + * copy node superclass fields + */ + CopyPlanFields((const Plan *) from, (Plan *) newnode); + + return newnode; +} + +/* * _copyModifyTable */ static ModifyTable * @@ -4413,6 +4429,9 @@ copyObject(const void *from) case T_Result: retval = _copyResult(from); break; + case T_SetResult: + retval = _copySetResult(from); + break; case T_ModifyTable: retval = _copyModifyTable(from); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 73fdc9706d..6a1b9a4536 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -327,6 +327,14 @@ _outResult(StringInfo str, const Result *node) } static void +_outSetResult(StringInfo str, const SetResult *node) +{ + WRITE_NODE_TYPE("SETRESULT"); + + _outPlanInfo(str, (const Plan *) node); +} + +static void _outModifyTable(StringInfo str, const ModifyTable *node) { WRITE_NODE_TYPE("MODIFYTABLE"); @@ -1805,7 +1813,6 @@ _outProjectionPath(StringInfo str, const ProjectionPath *node) WRITE_NODE_FIELD(subpath); WRITE_BOOL_FIELD(dummypp); - WRITE_BOOL_FIELD(srfpp); } static void @@ -3362,6 +3369,9 @@ outNode(StringInfo str, const void *obj) case T_Result: _outResult(str, obj); break; + case T_SetResult: + _outSetResult(str, obj); + break; case T_ModifyTable: _outModifyTable(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index e02dd94f05..f47b841947 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1483,6 +1483,20 @@ _readResult(void) READ_DONE(); } + +/* + * _readSetResult + */ +static SetResult * +_readSetResult(void) +{ + READ_LOCALS_NO_FIELDS(SetResult); + + ReadCommonPlan(&local_node->plan); + + READ_DONE(); +} + /* * _readModifyTable */ @@ -2450,6 +2464,8 @@ parseNodeString(void) return_value = _readPlan(); else if (MATCH("RESULT", 6)) return_value = _readResult(); + else if (MATCH("SETRESULT", 9)) + return_value = _readSetResult(); else if (MATCH("MODIFYTABLE", 11)) return_value = _readModifyTable(); else if (MATCH("APPEND", 6)) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 46d7d064d4..1708e8062c 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -2976,6 +2976,9 @@ print_path(PlannerInfo *root, Path *path, int indent) case T_ResultPath: ptype = "Result"; break; + case T_SetResultPath: + ptype = "SetResult"; + break; case T_MaterialPath: ptype = "Material"; subpath = ((MaterialPath *) path)->subpath; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 875de739a8..78f9d1b4c3 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -81,6 +81,7 @@ static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path); static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path); static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path); static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path); +static SetResult *create_set_result_plan(PlannerInfo *root, SetProjectionPath *best_path); static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags); static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path, @@ -264,6 +265,7 @@ static SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree, long numGroups); static LockRows *make_lockrows(Plan *lefttree, List *rowMarks, int epqParam); static Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan); +static SetResult *make_set_result(List *tlist, Plan *subplan); static ModifyTable *make_modifytable(PlannerInfo *root, CmdType operation, bool canSetTag, Index nominalRelation, @@ -392,6 +394,10 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags) (ResultPath *) best_path); } break; + case T_SetResult: + plan = (Plan *) create_set_result_plan(root, + (SetProjectionPath *) best_path); + break; case T_Material: plan = (Plan *) create_material_plan(root, (MaterialPath *) best_path, @@ -1142,6 +1148,44 @@ create_result_plan(PlannerInfo *root, ResultPath *best_path) } /* + * create_set_result_plan + * Create a SetResult plan for 'best_path'. + * + * Returns a Plan node. + */ +static SetResult * +create_set_result_plan(PlannerInfo *root, SetProjectionPath *best_path) +{ + SetResult *plan; + Plan *subplan; + List *tlist; + + /* + * XXX Possibly-temporary hack: if the subpath is a dummy ResultPath, + * don't bother with it, just make a SetResult with no input. This avoids + * an extra Result plan node when doing "SELECT srf()". Depending on what + * we decide about the desired plan structure for SRF-expanding nodes, + * this optimization might have to go away, and in any case it'll probably + * look a good bit different. + */ + if (IsA(best_path->subpath, ResultPath) && + ((ResultPath *) best_path->subpath)->path.pathtarget->exprs == NIL && + ((ResultPath *) best_path->subpath)->quals == NIL) + subplan = NULL; + else + /* Since we intend to project, we don't need to constrain child tlist */ + subplan = create_plan_recurse(root, best_path->subpath, 0); + + tlist = build_path_tlist(root, &best_path->path); + + plan = make_set_result(tlist, subplan); + + copy_generic_path_info(&plan->plan, (Path *) best_path); + + return plan; +} + +/* * create_material_plan * Create a Material plan for 'best_path' and (recursively) plans * for its subpaths. @@ -1421,21 +1465,8 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path) Plan *subplan; List *tlist; - /* - * XXX Possibly-temporary hack: if the subpath is a dummy ResultPath, - * don't bother with it, just make a Result with no input. This avoids an - * extra Result plan node when doing "SELECT srf()". Depending on what we - * decide about the desired plan structure for SRF-expanding nodes, this - * optimization might have to go away, and in any case it'll probably look - * a good bit different. - */ - if (IsA(best_path->subpath, ResultPath) && - ((ResultPath *) best_path->subpath)->path.pathtarget->exprs == NIL && - ((ResultPath *) best_path->subpath)->quals == NIL) - subplan = NULL; - else - /* Since we intend to project, we don't need to constrain child tlist */ - subplan = create_plan_recurse(root, best_path->subpath, 0); + /* Since we intend to project, we don't need to constrain child tlist */ + subplan = create_plan_recurse(root, best_path->subpath, 0); tlist = build_path_tlist(root, &best_path->path); @@ -1454,9 +1485,8 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path) * creation, but that would add expense to creating Paths we might end up * not using.) */ - if (!best_path->srfpp && - (is_projection_capable_path(best_path->subpath) || - tlist_same_exprs(tlist, subplan->targetlist))) + if (is_projection_capable_path(best_path->subpath) || + tlist_same_exprs(tlist, subplan->targetlist)) { /* Don't need a separate Result, just assign tlist to subplan */ plan = subplan; @@ -6041,6 +6071,25 @@ make_result(List *tlist, } /* + * make_set_result + * Build a SetResult plan node + */ +static SetResult * +make_set_result(List *tlist, + Plan *subplan) +{ + SetResult *node = makeNode(SetResult); + Plan *plan = &node->plan; + + plan->targetlist = tlist; + plan->qual = NIL; + plan->lefttree = subplan; + plan->righttree = NULL; + + return node; +} + +/* * make_modifytable * Build a ModifyTable plan node */ @@ -6206,17 +6255,15 @@ is_projection_capable_path(Path *path) * projection to its dummy path. */ return IS_DUMMY_PATH(path); - case T_Result: + case T_SetResult: /* * If the path is doing SRF evaluation, claim it can't project, so * we don't jam a new tlist into it and thereby break the property * that the SRFs appear at top level. */ - if (IsA(path, ProjectionPath) && - ((ProjectionPath *) path)->srfpp) - return false; - break; + return false; + default: break; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 70870bbbe0..a208f511d9 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -5245,7 +5245,7 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel, /* If this level doesn't contain SRFs, do regular projection */ if (contains_srfs) - newpath = (Path *) create_srf_projection_path(root, + newpath = (Path *) create_set_projection_path(root, rel, newpath, thistarget); @@ -5278,7 +5278,7 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel, /* If this level doesn't contain SRFs, do regular projection */ if (contains_srfs) - newpath = (Path *) create_srf_projection_path(root, + newpath = (Path *) create_set_projection_path(root, rel, newpath, thistarget); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 413a0d9da2..e77312d6af 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -733,6 +733,27 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) fix_scan_expr(root, splan->resconstantqual, rtoffset); } break; + + case T_SetResult: + { + SetResult *splan = (SetResult *) plan; + + /* + * SetResult may or may not have a subplan; if not, it's more + * like a scan node than an upper node. + */ + if (splan->plan.lefttree != NULL) + set_upper_references(root, plan, rtoffset); + else + { + splan->plan.targetlist = + fix_scan_list(root, splan->plan.targetlist, rtoffset); + splan->plan.qual = + fix_scan_list(root, splan->plan.qual, rtoffset); + } + } + break; + case T_ModifyTable: { ModifyTable *splan = (ModifyTable *) plan; diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index aad0b684ed..ad8b75b4d9 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2680,6 +2680,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, &context); break; + case T_SetResult: case T_Hash: case T_Material: case T_Sort: diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index aa635fd057..2e30af20af 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -2227,9 +2227,6 @@ create_projection_path(PlannerInfo *root, (cpu_tuple_cost + target->cost.per_tuple) * subpath->rows; } - /* Assume no SRFs around */ - pathnode->srfpp = false; - return pathnode; } @@ -2333,17 +2330,17 @@ apply_projection_to_path(PlannerInfo *root, * 'subpath' is the path representing the source of data * 'target' is the PathTarget to be computed */ -ProjectionPath * -create_srf_projection_path(PlannerInfo *root, +SetProjectionPath * +create_set_projection_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, PathTarget *target) { - ProjectionPath *pathnode = makeNode(ProjectionPath); + SetProjectionPath *pathnode = makeNode(SetProjectionPath); double tlist_rows; ListCell *lc; - pathnode->path.pathtype = T_Result; + pathnode->path.pathtype = T_SetResult; pathnode->path.parent = rel; pathnode->path.pathtarget = target; /* For now, assume we are above any joins, so no parameterization */ @@ -2353,15 +2350,11 @@ create_srf_projection_path(PlannerInfo *root, subpath->parallel_safe && is_parallel_safe(root, (Node *) target->exprs); pathnode->path.parallel_workers = subpath->parallel_workers; - /* Projection does not change the sort order */ + /* Projection does not change the sort order XXX? */ pathnode->path.pathkeys = subpath->pathkeys; pathnode->subpath = subpath; - /* Always need the Result node */ - pathnode->dummypp = false; - pathnode->srfpp = true; - /* * Estimate number of rows produced by SRFs for each row of input; if * there's more than one in this node, use the maximum. diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index 4e92ebdf41..8290769468 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -776,7 +776,7 @@ apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target) * Split given PathTarget into multiple levels to position SRFs safely * * The executor can only handle set-returning functions that appear at the - * top level of the targetlist of a Result plan node. If we have any SRFs + * top level of the targetlist of a SetResult plan node. If we have any SRFs * that are not at top level, we need to split up the evaluation into multiple * plan levels in which each level satisfies this constraint. This function * creates appropriate PathTarget(s) for each level. diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index b9c7f72903..4e48592798 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -253,6 +253,10 @@ extern Tuplestorestate *ExecMakeTableFunctionResult(ExprState *funcexpr, MemoryContext argContext, TupleDesc expectedDesc, bool randomAccess); +extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone); extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); diff --git a/src/include/executor/nodeSetResult.h b/src/include/executor/nodeSetResult.h new file mode 100644 index 0000000000..f51cf32956 --- /dev/null +++ b/src/include/executor/nodeSetResult.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * nodeSetResult.h + * + * + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/executor/nodeSetResult.h + * + *------------------------------------------------------------------------- + */ +#ifndef NODESETRESULT_H +#define NODESETRESULT_H + +#include "nodes/execnodes.h" + +extern SetResultState *ExecInitSetResult(SetResult *node, EState *estate, int eflags); +extern TupleTableSlot *ExecSetResult(SetResultState *node); +extern void ExecEndSetResult(SetResultState *node); +extern void ExecReScanSetResult(SetResultState *node); + +#endif /* NODESETRESULT_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index ce13bf7635..69de3ebbd9 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1129,6 +1129,21 @@ typedef struct ResultState bool rs_checkqual; /* do we need to check the qual? */ } ResultState; + +/* ---------------- + * SetResultState information + * ---------------- + */ +typedef struct SetResultState +{ + PlanState ps; /* its first field is NodeTag */ + int nelems; + ExprDoneCond *elemdone; + bool input_done; /* done reading source tuple? */ + bool pending_srf_tuples; /* evaluating srfs in tlist? */ +} SetResultState; + + /* ---------------- * ModifyTableState information * ---------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 4c4319bcab..be397fb138 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -43,6 +43,7 @@ typedef enum NodeTag */ T_Plan, T_Result, + T_SetResult, T_ModifyTable, T_Append, T_MergeAppend, @@ -91,6 +92,7 @@ typedef enum NodeTag */ T_PlanState, T_ResultState, + T_SetResultState, T_ModifyTableState, T_AppendState, T_MergeAppendState, @@ -245,6 +247,7 @@ typedef enum NodeTag T_UniquePath, T_GatherPath, T_ProjectionPath, + T_SetProjectionPath, T_SortPath, T_GroupPath, T_UpperUniquePath, diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 6810f8c099..3405f018fc 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -176,6 +176,13 @@ typedef struct Result Node *resconstantqual; } Result; + +typedef struct SetResult +{ + Plan plan; +} SetResult; + + /* ---------------- * ModifyTable node - * Apply rows produced by subplan(s) to result table(s), diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index de4092d679..50fa79926a 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -1293,10 +1293,19 @@ typedef struct ProjectionPath Path path; Path *subpath; /* path representing input source */ bool dummypp; /* true if no separate Result is needed */ - bool srfpp; /* true if SRFs are being evaluated here */ } ProjectionPath; /* + * SetProjectionPath represents an evaluation of a targetlist set returning + * function. + */ +typedef struct SetProjectionPath +{ + Path path; + Path *subpath; /* path representing input source */ +} SetProjectionPath; + +/* * SortPath represents an explicit sort step * * The sort keys are, by definition, the same as path.pathkeys. diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index c11c59df23..9cbd87c0a2 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -144,7 +144,7 @@ extern Path *apply_projection_to_path(PlannerInfo *root, RelOptInfo *rel, Path *path, PathTarget *target); -extern ProjectionPath *create_srf_projection_path(PlannerInfo *root, +extern SetProjectionPath *create_set_projection_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, PathTarget *target); diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index b71d81ee21..c7a87a25a9 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -822,7 +822,7 @@ explain (costs off) -> Limit -> Index Only Scan Backward using tenk1_unique2 on tenk1 Index Cond: (unique2 IS NOT NULL) - -> Result + -> SetResult -> Result (8 rows) diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out index a7ded3ad05..f3124394a3 100644 --- a/src/test/regress/expected/limit.out +++ b/src/test/regress/expected/limit.out @@ -212,7 +212,7 @@ select unique1, unique2, generate_series(1,10) ------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: unique1, unique2, (generate_series(1, 10)) - -> Result + -> SetResult Output: unique1, unique2, generate_series(1, 10) -> Index Scan using tenk1_unique2 on public.tenk1 Output: unique1, unique2, two, four, ten, twenty, hundred, thousand, twothousand, fivethous, tenthous, odd, even, stringu1, stringu2, string4 @@ -238,7 +238,7 @@ select unique1, unique2, generate_series(1,10) -------------------------------------------------------------------- Limit Output: unique1, unique2, (generate_series(1, 10)), tenthous - -> Result + -> SetResult Output: unique1, unique2, generate_series(1, 10), tenthous -> Sort Output: unique1, unique2, tenthous @@ -265,7 +265,7 @@ explain (verbose, costs off) select generate_series(0,2) as s1, generate_series((random()*.1)::int,2) as s2; QUERY PLAN ------------------------------------------------------------------------------------------------------ - Result + SetResult Output: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2) (2 rows) @@ -285,7 +285,7 @@ order by s2 desc; Sort Output: (generate_series(0, 2)), (generate_series(((random() * '0.1'::double precision))::integer, 2)) Sort Key: (generate_series(((random() * '0.1'::double precision))::integer, 2)) DESC - -> Result + -> SetResult Output: generate_series(0, 2), generate_series(((random() * '0.1'::double precision))::integer, 2) (5 rows) diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out index 3ae918a63c..b49fa17eb3 100644 --- a/src/test/regress/expected/portals.out +++ b/src/test/regress/expected/portals.out @@ -1322,14 +1322,14 @@ begin; explain (costs off) declare c2 cursor for select generate_series(1,3) as g; QUERY PLAN ------------ - Result + SetResult (1 row) explain (costs off) declare c2 scroll cursor for select generate_series(1,3) as g; - QUERY PLAN --------------- + QUERY PLAN +----------------- Materialize - -> Result + -> SetResult (2 rows) declare c2 scroll cursor for select generate_series(1,3) as g; diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 3ed089aa46..0215c9a663 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -821,7 +821,7 @@ select * from int4_tbl o where (f1, f1) in Filter: ("ANY_subquery".f1 = "ANY_subquery".g) -> Result Output: i.f1, ((generate_series(1, 2)) / 10) - -> Result + -> SetResult Output: i.f1, generate_series(1, 2) -> HashAggregate Output: i.f1 @@ -903,7 +903,7 @@ select * from Subquery Scan on ss Output: x, u Filter: tattle(ss.x, 8) - -> Result + -> SetResult Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) (5 rows) @@ -934,10 +934,11 @@ select * from where tattle(x, 8); QUERY PLAN ---------------------------------------------------- - Result + SetResult Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) - One-Time Filter: tattle(9, 8) -(3 rows) + -> Result + One-Time Filter: tattle(9, 8) +(4 rows) select * from (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss @@ -963,7 +964,7 @@ select * from Subquery Scan on ss Output: x, u Filter: tattle(ss.x, ss.u) - -> Result + -> SetResult Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) (5 rows) diff --git a/src/test/regress/expected/tsrf.out b/src/test/regress/expected/tsrf.out index f257537925..8c47f0f668 100644 --- a/src/test/regress/expected/tsrf.out +++ b/src/test/regress/expected/tsrf.out @@ -25,8 +25,8 @@ SELECT generate_series(1, 2), generate_series(1,4); -----------------+----------------- 1 | 1 2 | 2 - 1 | 3 - 2 | 4 + | 3 + | 4 (4 rows) -- srf, with SRF argument @@ -127,15 +127,15 @@ SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa, unnest('{1,1,3}'::int[]); dataa | count | min | max | unnest -------+-------+-----+-----+-------- - a | 2 | 1 | 1 | 1 a | 1 | 1 | 1 | 3 + a | 2 | 1 | 1 | 1 (2 rows) SELECT few.dataa, count(*), min(id), max(id), unnest('{1,1,3}'::int[]) FROM few WHERE few.id = 1 GROUP BY few.dataa, 5; dataa | count | min | max | unnest -------+-------+-----+-----+-------- - a | 2 | 1 | 1 | 1 a | 1 | 1 | 1 | 3 + a | 2 | 1 | 1 | 1 (2 rows) -- check HAVING works when GROUP BY does [not] reference SRF output diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index 67f5fc4361..743d0bd0ed 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -636,7 +636,7 @@ ORDER BY x; -> HashAggregate Group Key: (1), (generate_series(1, 10)) -> Append - -> Result + -> SetResult -> Result (9 rows) -- 2.11.0.22.g8d7a455.dirty