diff options
Diffstat (limited to 'src/backend/executor/functions.c')
-rw-r--r-- | src/backend/executor/functions.c | 153 |
1 files changed, 95 insertions, 58 deletions
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index d3f05c7d2c7..b264060d33d 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -55,7 +55,9 @@ typedef struct * * The "next" fields chain together all the execution_state records generated * from a single original parsetree. (There will only be more than one in - * case of rule expansion of the original parsetree.) + * case of rule expansion of the original parsetree.) The chain structure is + * quite vestigial at this point, because we allocate the records in an array + * for ease of memory management. But we'll get rid of it some other day. */ typedef enum { @@ -158,17 +160,20 @@ typedef struct SQLFunctionCache /* * While executing a particular query within the function, cplan is the - * CachedPlan we've obtained for that query, and eslist is a list of + * CachedPlan we've obtained for that query, and eslist is a chain of * execution_state records for the individual plans within the CachedPlan. * next_query_index is the 0-based index of the next CachedPlanSource to * get a CachedPlan from. */ CachedPlan *cplan; /* Plan for current query, if any */ ResourceOwner cowner; /* CachedPlan is registered with this owner */ - execution_state *eslist; /* execution_state records */ int next_query_index; /* index of next CachedPlanSource to run */ - /* if positive, this is the index of the query we're processing */ + execution_state *eslist; /* chain of execution_state records */ + execution_state *esarray; /* storage for eslist */ + int esarray_len; /* allocated length of esarray[] */ + + /* if positive, this is the 1-based index of the query we're processing */ int error_query_index; MemoryContext fcontext; /* memory context holding this struct and all @@ -212,13 +217,12 @@ static void sql_delete_callback(CachedFunction *cfunc); static void sql_postrewrite_callback(List *querytree_list, void *arg); static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache); static bool postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache); -static void postquel_end(execution_state *es); +static void postquel_end(execution_state *es, SQLFunctionCachePtr fcache); static void postquel_sub_params(SQLFunctionCachePtr fcache, FunctionCallInfo fcinfo); static Datum postquel_get_single_result(TupleTableSlot *slot, FunctionCallInfo fcinfo, - SQLFunctionCachePtr fcache, - MemoryContext resultcontext); + SQLFunctionCachePtr fcache); static void sql_compile_error_callback(void *arg); static void sql_exec_error_callback(void *arg); static void ShutdownSQLFunction(Datum arg); @@ -658,12 +662,11 @@ init_execution_state(SQLFunctionCachePtr fcache) CachedPlanSource *plansource; execution_state *preves = NULL; execution_state *lasttages = NULL; + int nstmts; ListCell *lc; /* - * Clean up after previous query, if there was one. Note that we just - * leak the old execution_state records until end of function execution; - * there aren't likely to be enough of them to matter. + * Clean up after previous query, if there was one. */ if (fcache->cplan) { @@ -705,6 +708,22 @@ init_execution_state(SQLFunctionCachePtr fcache) NULL); /* + * If necessary, make esarray[] bigger to hold the needed state. + */ + nstmts = list_length(fcache->cplan->stmt_list); + if (nstmts > fcache->esarray_len) + { + if (fcache->esarray == NULL) + fcache->esarray = (execution_state *) + MemoryContextAlloc(fcache->fcontext, + sizeof(execution_state) * nstmts); + else + fcache->esarray = repalloc_array(fcache->esarray, + execution_state, nstmts); + fcache->esarray_len = nstmts; + } + + /* * Build execution_state list to match the number of contained plans. */ foreach(lc, fcache->cplan->stmt_list) @@ -740,7 +759,7 @@ init_execution_state(SQLFunctionCachePtr fcache) CreateCommandName((Node *) stmt)))); /* OK, build the execution_state for this query */ - newes = (execution_state *) palloc(sizeof(execution_state)); + newes = &fcache->esarray[foreach_current_index(lc)]; if (preves) preves->next = newes; else @@ -775,9 +794,14 @@ init_execution_state(SQLFunctionCachePtr fcache) */ if (fcache->func->rettype != VOIDOID) { - TupleTableSlot *slot = MakeSingleTupleTableSlot(NULL, - &TTSOpsMinimalTuple); + TupleTableSlot *slot; List *resulttlist; + MemoryContext oldcontext; + + /* The result slot and JunkFilter must be in the fcontext */ + oldcontext = MemoryContextSwitchTo(fcache->fcontext); + + slot = MakeSingleTupleTableSlot(NULL, &TTSOpsMinimalTuple); /* * Re-fetch the (possibly modified) output tlist of the final @@ -811,14 +835,17 @@ init_execution_state(SQLFunctionCachePtr fcache) * pointer. */ fcache->junkFilter->jf_targetList = NIL; - } - if (fcache->func->returnsTuple) - { /* Make sure output rowtype is properly blessed */ - BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor); + if (fcache->func->returnsTuple) + BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor); + + MemoryContextSwitchTo(oldcontext); } - else if (fcache->func->returnsSet && type_is_rowtype(fcache->func->rettype)) + + if (fcache->func->returnsSet && + !fcache->func->returnsTuple && + type_is_rowtype(fcache->func->rettype)) { /* * Returning rowtype as if it were scalar --- materialize won't work. @@ -1230,6 +1257,7 @@ static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache) { DestReceiver *dest; + MemoryContext oldcontext; Assert(es->qd == NULL); @@ -1237,6 +1265,16 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) Assert(ActiveSnapshotSet()); /* + * Run the sub-executor in a child of fcontext. The sub-executor is + * responsible for deleting per-tuple information. (XXX in the case of a + * long-lived FmgrInfo, this policy potentially causes memory leakage, but + * it's not very clear where we could keep stuff instead. Fortunately, + * there are few if any cases where set-returning functions are invoked + * via FmgrInfos that would outlive the calling query.) + */ + oldcontext = MemoryContextSwitchTo(fcache->fcontext); + + /* * If this query produces the function result, send its output to the * tuplestore; else discard any output. */ @@ -1285,6 +1323,8 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) } es->status = F_EXEC_RUN; + + MemoryContextSwitchTo(oldcontext); } /* Run one execution_state; either to completion or to first result row */ @@ -1293,6 +1333,10 @@ static bool postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache) { bool result; + MemoryContext oldcontext; + + /* Run the sub-executor in a child of fcontext */ + oldcontext = MemoryContextSwitchTo(fcache->fcontext); if (es->qd->operation == CMD_UTILITY) { @@ -1320,13 +1364,20 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache) result = (count == 0 || es->qd->estate->es_processed == 0); } + MemoryContextSwitchTo(oldcontext); + return result; } /* Shut down execution of one execution_state node */ static void -postquel_end(execution_state *es) +postquel_end(execution_state *es, SQLFunctionCachePtr fcache) { + MemoryContext oldcontext; + + /* Run the sub-executor in a child of fcontext */ + oldcontext = MemoryContextSwitchTo(fcache->fcontext); + /* mark status done to ensure we don't do ExecutorEnd twice */ es->status = F_EXEC_DONE; @@ -1341,9 +1392,16 @@ postquel_end(execution_state *es) FreeQueryDesc(es->qd); es->qd = NULL; + + MemoryContextSwitchTo(oldcontext); } -/* Build ParamListInfo array representing current arguments */ +/* + * Build ParamListInfo array representing current arguments. + * + * This must be called in the fcontext so that the results have adequate + * lifespan. + */ static void postquel_sub_params(SQLFunctionCachePtr fcache, FunctionCallInfo fcinfo) @@ -1398,25 +1456,22 @@ postquel_sub_params(SQLFunctionCachePtr fcache, /* * Extract the SQL function's value from a single result row. This is used * both for scalar (non-set) functions and for each row of a lazy-eval set - * result. + * result. We expect the current memory context to be that of the caller + * of fmgr_sql. */ static Datum postquel_get_single_result(TupleTableSlot *slot, FunctionCallInfo fcinfo, - SQLFunctionCachePtr fcache, - MemoryContext resultcontext) + SQLFunctionCachePtr fcache) { Datum value; - MemoryContext oldcontext; /* * Set up to return the function value. For pass-by-reference datatypes, - * be sure to allocate the result in resultcontext, not the current memory - * context (which has query lifespan). We can't leave the data in the - * TupleTableSlot because we intend to clear the slot before returning. + * be sure to copy the result into the current context. We can't leave + * the data in the TupleTableSlot because we intend to clear the slot + * before returning. */ - oldcontext = MemoryContextSwitchTo(resultcontext); - if (fcache->func->returnsTuple) { /* We must return the whole tuple as a Datum. */ @@ -1427,7 +1482,7 @@ postquel_get_single_result(TupleTableSlot *slot, { /* * Returning a scalar, which we have to extract from the first column - * of the SELECT result, and then copy into result context if needed. + * of the SELECT result, and then copy into current context if needed. */ value = slot_getattr(slot, 1, &(fcinfo->isnull)); @@ -1435,8 +1490,6 @@ postquel_get_single_result(TupleTableSlot *slot, value = datumCopy(value, fcache->func->typbyval, fcache->func->typlen); } - MemoryContextSwitchTo(oldcontext); - return value; } @@ -1450,7 +1503,6 @@ fmgr_sql(PG_FUNCTION_ARGS) SQLFunctionLink *flink; ErrorContextCallback sqlerrcontext; MemoryContext tscontext; - MemoryContext oldcontext; bool randomAccess; bool lazyEvalOK; bool pushed_snapshot; @@ -1507,20 +1559,14 @@ fmgr_sql(PG_FUNCTION_ARGS) * Build tuplestore to hold results, if we don't have one already. Make * sure it's in a suitable context. */ - oldcontext = MemoryContextSwitchTo(tscontext); - if (!fcache->tstore) - fcache->tstore = tuplestore_begin_heap(randomAccess, false, work_mem); + { + MemoryContext oldcontext; - /* - * Switch to context in which the fcache lives. The sub-executor is - * responsible for deleting per-tuple information. (XXX in the case of a - * long-lived FmgrInfo, this policy potentially causes memory leakage, but - * it's not very clear where we could keep stuff instead. Fortunately, - * there are few if any cases where set-returning functions are invoked - * via FmgrInfos that would outlive the calling query.) - */ - MemoryContextSwitchTo(fcache->fcontext); + oldcontext = MemoryContextSwitchTo(tscontext); + fcache->tstore = tuplestore_begin_heap(randomAccess, false, work_mem); + MemoryContextSwitchTo(oldcontext); + } /* * Find first unfinished execution_state. If none, advance to the next @@ -1598,7 +1644,7 @@ fmgr_sql(PG_FUNCTION_ARGS) * don't care about fetching any more result rows. */ if (completed || !fcache->func->returnsSet) - postquel_end(es); + postquel_end(es, fcache); /* * Break from loop if we didn't shut down (implying we got a @@ -1657,8 +1703,7 @@ fmgr_sql(PG_FUNCTION_ARGS) if (!tuplestore_gettupleslot(fcache->tstore, true, false, slot)) elog(ERROR, "failed to fetch lazy-eval tuple"); /* Extract the result as a datum, and copy out from the slot */ - result = postquel_get_single_result(slot, fcinfo, - fcache, oldcontext); + result = postquel_get_single_result(slot, fcinfo, fcache); /* Clear the tuplestore, but keep it for next time */ /* NB: this might delete the slot's content, but we don't care */ tuplestore_clear(fcache->tstore); @@ -1716,12 +1761,7 @@ fmgr_sql(PG_FUNCTION_ARGS) fcache->tstore = NULL; /* must copy desc because execSRF.c will free it */ if (fcache->junkFilter) - { - /* setDesc must be allocated in suitable context */ - MemoryContextSwitchTo(tscontext); rsi->setDesc = CreateTupleDescCopy(fcache->junkFilter->jf_cleanTupType); - MemoryContextSwitchTo(fcache->fcontext); - } fcinfo->isnull = true; result = (Datum) 0; @@ -1746,8 +1786,7 @@ fmgr_sql(PG_FUNCTION_ARGS) /* Re-use the junkfilter's output slot to fetch back the tuple */ slot = fcache->junkFilter->jf_resultSlot; if (tuplestore_gettupleslot(fcache->tstore, true, false, slot)) - result = postquel_get_single_result(slot, fcinfo, - fcache, oldcontext); + result = postquel_get_single_result(slot, fcinfo, fcache); else { fcinfo->isnull = true; @@ -1770,8 +1809,6 @@ fmgr_sql(PG_FUNCTION_ARGS) if (pushed_snapshot) PopActiveSnapshot(); - MemoryContextSwitchTo(oldcontext); - /* * If we've gone through every command in the function, we are done. * Release the cache to start over again on next call. @@ -1889,7 +1926,7 @@ ShutdownSQLFunction(Datum arg) if (!fcache->func->readonly_func) PushActiveSnapshot(es->qd->snapshot); - postquel_end(es); + postquel_end(es, fcache); if (!fcache->func->readonly_func) PopActiveSnapshot(); |