--- src/include/nodes/execnodes.h 2019-08-05 23:16:54.000000000 +0200 +++ src/include/nodes/execnodes.h 2019-11-03 20:05:34.338305825 +0100 @@ -882,6 +883,39 @@ typedef struct PlanState TupleTableSlot *ps_ResultTupleSlot; /* slot for my result tuples */ ExprContext *ps_ExprContext; /* node's expression-evaluation context */ ProjectionInfo *ps_ProjInfo; /* info for doing tuple projection */ +#ifdef OPTFUNCALLS + /* was_called - list of ExprEvalStep* or FuncExpr* depending on execution stage + * + * Stage I. ExecInitExprRec() + * List gathers all not volatile, not set returning, not window FuncExpr*, + * equal nodes occupy one position in the list. Position in this list ( counting from 1 ) + * and planstate are remembered in actual ExprEvalStep* + * + * For query: select f(n),f(n) from t - was_called->length will be 1 and ptr_value + * will be FuncExpr* node of f(n) + * + * For query: select f(n),g(n),f(n) from t - list->length == 2 + * + * Stage II. ExecProcnode() + * For every planstate->was_called list changes its interpretation - from now on + * it is a list of ExprEvalStep* . Before executing real execProcnode + * every element of this list ( ptr_value ) is set to NULL. We don't know which + * function will be called first + * + * Stage III. ExecInterpExpr() case EEOP_FUNCEXPR + * ExprEvalStep.position > 0 means that in planstate->was_called could be ExprEvalStep* + * which was done yet or NULL. + * + * NULL means that eval step is entered first time and: + * 1. real function must be called + * 2. ExprEvalStep has to be remembered in planstate->was_called at position + * step->position - 1 + * + * NOT NULL means that in planstate->was_called is ExprEvalStep* with ready result, so + * there is no need to call function + */ + List *was_called; +#endif } PlanState; /* ---------------- --- src/include/executor/execExpr.h 2019-08-05 23:16:54.000000000 +0200 +++ src/include/executor/execExpr.h 2019-11-03 20:04:03.739025142 +0100 @@ -561,6 +561,10 @@ typedef struct ExprEvalStep AlternativeSubPlanState *asstate; } alternative_subplan; } d; +#ifdef OPTFUNCALLS + PlanState *planstate; /* parent PlanState for this expression */ + int position; /* position in planstate->was_called counted from 1 */ +#endif } ExprEvalStep; --- src/backend/executor/execProcnode.c 2019-08-05 23:16:54.000000000 +0200 +++ src/backend/executor/execProcnode.c 2019-11-03 19:54:28.071672386 +0100 @@ -120,6 +120,17 @@ static TupleTableSlot *ExecProcNodeFirst(PlanState *node); static TupleTableSlot *ExecProcNodeInstr(PlanState *node); +#ifdef OPTFUNCALLS +static TupleTableSlot *execReal(PlanState *node) +{ + /* Before each scan step, node->was_called elements must be set to NULL */ + ListCell *item; + foreach(item,node->was_called) + item->data.ptr_value = NULL; + + return node->ExecProcNodeReal(node); +} +#endif /* ------------------------------------------------------------------------ * ExecInitNode @@ -425,8 +436,11 @@ ExecProcNodeFirst(PlanState *node) if (node->instrument) node->ExecProcNode = ExecProcNodeInstr; else +#ifndef OPTFUNCALLS node->ExecProcNode = node->ExecProcNodeReal; - +#else + node->ExecProcNode = execReal; +#endif return node->ExecProcNode(node); } @@ -442,9 +456,11 @@ ExecProcNodeInstr(PlanState *node) TupleTableSlot *result; InstrStartNode(node->instrument); - +#ifndef OPTFUNCALLS result = node->ExecProcNodeReal(node); - +#else + result = execReal(node); +#endif InstrStopNode(node->instrument, TupIsNull(result) ? 0.0 : 1.0); return result; --- src/backend/executor/execExpr.c 2019-08-05 23:16:54.000000000 +0200 +++ src/backend/executor/execExpr.c 2019-11-03 19:57:21.994249398 +0100 @@ -45,7 +45,13 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/typcache.h" - +#ifdef OPTFUNCALLS +#include "catalog/pg_proc.h" +#include "utils/syscache.h" +#include "access/htup_details.h" +static bool isNotVolatile(Oid funcid); +static int findFuncExpr(FuncExpr* node,PlanState* parent); +#endif typedef struct LastAttnumInfo { @@ -806,7 +812,40 @@ ExecInitExprRec(Expr *node, PlanState *p ExecInitFunc(&scratch, node, func->args, func->funcid, func->inputcollid, parent, state); +#ifdef OPTFUNCALLS + scratch.position = 0; + scratch.planstate = parent; + if( parent ) + { + /* Build/extend the list of non volatile functions for this PlanState node. + * Try to find func ( equal node ) in parent->was_called list at first + */ + int pos = findFuncExpr(func,parent); + if( !pos && isNotVolatile(func->funcid )) + { + /* Function is not in the list yet but it's maybe useful for optimizing + * repeated calls - register function in parent and remember its position + */ + parent->was_called = lappend(parent->was_called,func); + scratch.position = parent->was_called->length; + } + else if( pos ) + /* It is repeated call. Identical function is in planstate->was_called + * remember its position. + */ + scratch.position = pos; + + /* After ExecInitExprRec of all nodes each PlanState has initialized + * was_called list. In cells of these lists are FuncExpr nodes + * BUT THESE NODES ARE NOT NEEDED ANYMORE, we need only preallocated list + * in parent. Rest information is in ExprEvalStep ( EVStep for short ) position + * and planstate == parent. EVSteps with the same position are kind of family that + * has one common result after evaluation only one member. + */ + } +#endif ExprEvalPushStep(state, &scratch); + break; } @@ -2696,3 +2735,48 @@ ExecInitCoerceToDomain(ExprEvalStep *scr } } } + +#ifdef OPTFUNCALLS +/* Well, this function must exist till the moment when developers decide, that struct FuncExpr + * should also have provolatile field + */ +static bool isNotVolatile(Oid funcid) +{ + HeapTuple func_tuple; + Form_pg_proc func_form; + bool result; + + func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(func_tuple)) + elog(ERROR, "cache lookup failed for function %u", funcid); + func_form = (Form_pg_proc) GETSTRUCT(func_tuple); + + if( !(func_form->proisagg || func_form->proiswindow || func_form->proretset ) && func_form->provolatile != PROVOLATILE_VOLATILE ) + result = true; + else + result = false; + + ReleaseSysCache(func_tuple); + + return result; +} + + +/* Find FuncExpr in PlanState.was_called list + * comparing nodes not node pointers + */ +static int findFuncExpr(FuncExpr* node,PlanState* parent) +{ + ListCell *item; + int i = 0; + foreach(item,parent->was_called) + { + FuncExpr *fe = (FuncExpr*)lfirst(item); + i++; + + if( equal(node, fe ) ) + return i; + } + return 0; +} +#endif --- src/backend/executor/execExprInterp.c 2019-08-05 23:16:54.000000000 +0200 +++ src/backend/executor/execExprInterp.c 2019-11-03 19:56:32.906648836 +0100 @@ -644,12 +644,33 @@ ExecInterpExpr(ExprState *state, ExprCon */ EEO_CASE(EEOP_FUNCEXPR) { - FunctionCallInfo fcinfo = op->d.func.fcinfo_data; - - fcinfo->isnull = false; - *op->resvalue = (op->d.func.fn_addr) (fcinfo); - *op->resnull = fcinfo->isnull; +#ifdef OPTFUNCALLS + /* Check if this is first call of a function ( not window function and not returning set ) */ + ExprEvalStep* done; + if( op->position && (done = (ExprEvalStep*)list_nth(op->planstate->was_called,op->position - 1)) ) + { + /* it is repeated call, so get result from done */ + *op->resvalue = *done->resvalue; + *op->resnull = *done->resnull; + } + else + { +#endif + /* it is first call of function which can be optimized ( position > 0 ) + * or first/next call of function which can't be optimized ( position == 0 ) + * call real function + */ + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + fcinfo->isnull = false; + *op->resvalue = (op->d.func.fn_addr) (fcinfo); + *op->resnull = fcinfo->isnull; +#ifdef OPTFUNCALLS + if( op->position ) + /* be the source of result for other functions having position == op->position */ + list_nth_cell(op->planstate->was_called,op->position - 1)->data.ptr_value = op; + } +#endif EEO_NEXT(); }