diff options
Diffstat (limited to 'src/backend/executor/functions.c')
-rw-r--r-- | src/backend/executor/functions.c | 1391 |
1 files changed, 911 insertions, 480 deletions
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 6aa8e9c4d8a..5509013521d 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -31,6 +31,7 @@ #include "tcop/utility.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/funccache.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/snapmgr.h" @@ -50,7 +51,7 @@ typedef struct /* * We have an execution_state record for each query in a function. Each - * record contains a plantree for its query. If the query is currently in + * record references a plantree for its query. If the query is currently in * F_EXEC_RUN state then there's a QueryDesc too. * * The "next" fields chain together all the execution_state records generated @@ -74,24 +75,49 @@ typedef struct execution_state /* - * An SQLFunctionCache record is built during the first call, - * and linked to from the fn_extra field of the FmgrInfo struct. + * Data associated with a SQL-language function is kept in three main + * data structures: * - * Note that currently this has only the lifespan of the calling query. - * Someday we should rewrite this code to use plancache.c to save parse/plan - * results for longer than that. + * 1. SQLFunctionHashEntry is a long-lived (potentially session-lifespan) + * struct that holds all the info we need out of the function's pg_proc row. + * In addition it holds pointers to CachedPlanSource(s) that manage creation + * of plans for the query(s) within the function. A SQLFunctionHashEntry is + * potentially shared across multiple concurrent executions of the function, + * so it must contain no execution-specific state; but its use_count must + * reflect the number of SQLFunctionLink structs pointing at it. + * If the function's pg_proc row is updated, we throw away and regenerate + * the SQLFunctionHashEntry and subsidiary data. (Also note that if the + * function is polymorphic or used as a trigger, there is a separate + * SQLFunctionHashEntry for each usage, so that we need consider only one + * set of relevant data types.) The struct itself is in memory managed by + * funccache.c, and its subsidiary data is kept in one of two contexts: + * * pcontext ("parse context") holds the raw parse trees or Query trees + * that we read from the pg_proc row. These will be converted to + * CachedPlanSources as they are needed. Once the last one is converted, + * pcontext can be freed. + * * hcontext ("hash context") holds everything else belonging to the + * SQLFunctionHashEntry. * - * Physically, though, the data has the lifespan of the FmgrInfo that's used - * to call the function, and there are cases (particularly with indexes) - * where the FmgrInfo might survive across transactions. We cannot assume - * that the parse/plan trees are good for longer than the (sub)transaction in - * which parsing was done, so we must mark the record with the LXID/subxid of - * its creation time, and regenerate everything if that's obsolete. To avoid - * memory leakage when we do have to regenerate things, all the data is kept - * in a sub-context of the FmgrInfo's fn_mcxt. + * 2. SQLFunctionCache lasts for the duration of a single execution of + * the SQL function. (In "lazyEval" mode, this might span multiple calls of + * fmgr_sql.) It holds a reference to the CachedPlan for the current query, + * and other data that is execution-specific. The SQLFunctionCache itself + * as well as its subsidiary data are kept in fcontext ("function context"), + * which we free at completion. In non-returnsSet mode, this is just a child + * of the call-time context. In returnsSet mode, it is made a child of the + * FmgrInfo's fn_mcxt so that it will survive between fmgr_sql calls. + * + * 3. SQLFunctionLink is a tiny struct that just holds pointers to + * the SQLFunctionHashEntry and the current SQLFunctionCache (if any). + * It is pointed to by the fn_extra field of the FmgrInfo struct, and is + * always allocated in the FmgrInfo's fn_mcxt. Its purpose is to reduce + * the cost of repeat lookups of the SQLFunctionHashEntry. */ -typedef struct + +typedef struct SQLFunctionHashEntry { + CachedFunction cfunc; /* fields managed by funccache.c */ + char *fname; /* function name (for error msgs) */ char *src; /* function body text (for error msgs) */ @@ -102,8 +128,27 @@ typedef struct bool typbyval; /* true if return type is pass by value */ bool returnsSet; /* true if returning multiple rows */ bool returnsTuple; /* true if returning whole tuple result */ - bool shutdown_reg; /* true if registered shutdown callback */ bool readonly_func; /* true to run in "read only" mode */ + char prokind; /* prokind from pg_proc row */ + + TupleDesc rettupdesc; /* result tuple descriptor */ + + List *source_list; /* RawStmts or Queries read from pg_proc */ + int num_queries; /* original length of source_list */ + bool raw_source; /* true if source_list contains RawStmts */ + + List *plansource_list; /* CachedPlanSources for fn's queries */ + + MemoryContext pcontext; /* memory context holding source_list */ + MemoryContext hcontext; /* memory context holding all else */ +} SQLFunctionHashEntry; + +typedef struct SQLFunctionCache +{ + SQLFunctionHashEntry *func; /* associated SQLFunctionHashEntry */ + + bool lazyEvalOK; /* true if lazyEval is safe */ + bool shutdown_reg; /* true if registered shutdown callback */ bool lazyEval; /* true if using lazyEval for result query */ ParamListInfo paramLI; /* Param list representing current args */ @@ -113,22 +158,39 @@ typedef struct JunkFilter *junkFilter; /* will be NULL if function returns VOID */ /* - * func_state is a List of execution_state records, each of which is the - * first for its original parsetree, with any additional records chained - * to it via the "next" fields. This sublist structure is needed to keep - * track of where the original query boundaries are. + * While executing a particular query within the function, cplan is the + * CachedPlan we've obtained for that query, and eslist is a list 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. */ - List *func_state; + 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 */ + int error_query_index; MemoryContext fcontext; /* memory context holding this struct and all * subsidiary data */ - - LocalTransactionId lxid; /* lxid in which cache was made */ - SubTransactionId subxid; /* subxid in which cache was made */ } SQLFunctionCache; typedef SQLFunctionCache *SQLFunctionCachePtr; +/* Struct pointed to by FmgrInfo.fn_extra for a SQL function */ +typedef struct SQLFunctionLink +{ + /* Permanent pointer to associated SQLFunctionHashEntry */ + SQLFunctionHashEntry *func; + + /* Transient pointer to SQLFunctionCache, used only if returnsSet */ + SQLFunctionCache *fcache; + + /* Callback to release our use-count on the SQLFunctionHashEntry */ + MemoryContextCallback mcb; +} SQLFunctionLink; + /* non-export function prototypes */ static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref); @@ -138,10 +200,17 @@ static Node *sql_fn_make_param(SQLFunctionParseInfoPtr pinfo, int paramno, int location); static Node *sql_fn_resolve_param_name(SQLFunctionParseInfoPtr pinfo, const char *paramname, int location); -static List *init_execution_state(List *queryTree_list, - SQLFunctionCachePtr fcache, - bool lazyEvalOK); -static void init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK); +static SQLFunctionCache *init_sql_fcache(FunctionCallInfo fcinfo, + bool lazyEvalOK); +static bool init_execution_state(SQLFunctionCachePtr fcache); +static void prepare_next_query(SQLFunctionHashEntry *func); +static void sql_compile_callback(FunctionCallInfo fcinfo, + HeapTuple procedureTuple, + const CachedFunctionHashKey *hashkey, + CachedFunction *cfunc, + bool forValidator); +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); @@ -151,13 +220,20 @@ static Datum postquel_get_single_result(TupleTableSlot *slot, FunctionCallInfo fcinfo, SQLFunctionCachePtr fcache, MemoryContext resultcontext); +static void sql_compile_error_callback(void *arg); static void sql_exec_error_callback(void *arg); static void ShutdownSQLFunction(Datum arg); +static void RemoveSQLFunctionLink(void *arg); +static void check_sql_fn_statement(List *queryTreeList); +static bool check_sql_stmt_retval(List *queryTreeList, + Oid rettype, TupleDesc rettupdesc, + char prokind, bool insertDroppedCols); static bool coerce_fn_result_column(TargetEntry *src_tle, Oid res_type, int32 res_typmod, bool tlist_is_modifiable, List **upper_tlist, bool *upper_tlist_nontrivial); +static List *get_sql_fn_result_tlist(List *queryTreeList); static void sqlfunction_startup(DestReceiver *self, int operation, TupleDesc typeinfo); static bool sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self); static void sqlfunction_shutdown(DestReceiver *self); @@ -455,99 +531,297 @@ sql_fn_resolve_param_name(SQLFunctionParseInfoPtr pinfo, } /* - * Set up the per-query execution_state records for a SQL function. + * Initialize the SQLFunctionCache for a SQL function + */ +static SQLFunctionCache * +init_sql_fcache(FunctionCallInfo fcinfo, bool lazyEvalOK) +{ + FmgrInfo *finfo = fcinfo->flinfo; + SQLFunctionHashEntry *func; + SQLFunctionCache *fcache; + SQLFunctionLink *flink; + MemoryContext pcontext; + MemoryContext fcontext; + MemoryContext oldcontext; + + /* + * If this is the first execution for this FmgrInfo, set up a link struct + * (initially containing null pointers). The link must live as long as + * the FmgrInfo, so it goes in fn_mcxt. Also set up a memory context + * callback that will be invoked when fn_mcxt is deleted. + */ + flink = finfo->fn_extra; + if (flink == NULL) + { + flink = (SQLFunctionLink *) + MemoryContextAllocZero(finfo->fn_mcxt, sizeof(SQLFunctionLink)); + flink->mcb.func = RemoveSQLFunctionLink; + flink->mcb.arg = flink; + MemoryContextRegisterResetCallback(finfo->fn_mcxt, &flink->mcb); + finfo->fn_extra = flink; + } + + /* + * If we are resuming execution of a set-returning function, just keep + * using the same cache. We do not ask funccache.c to re-validate the + * SQLFunctionHashEntry: we want to run to completion using the function's + * initial definition. + */ + if (flink->fcache != NULL) + { + Assert(flink->fcache->func == flink->func); + return flink->fcache; + } + + /* + * Look up, or re-validate, the long-lived hash entry. Make the hash key + * depend on the result of get_call_result_type() when that's composite, + * so that we can safely assume that we'll build a new hash entry if the + * composite rowtype changes. + */ + func = (SQLFunctionHashEntry *) + cached_function_compile(fcinfo, + (CachedFunction *) flink->func, + sql_compile_callback, + sql_delete_callback, + sizeof(SQLFunctionHashEntry), + true, + false); + + /* + * Install the hash pointer in the SQLFunctionLink, and increment its use + * count to reflect that. If cached_function_compile gave us back a + * different hash entry than we were using before, we must decrement that + * one's use count. + */ + if (func != flink->func) + { + if (flink->func != NULL) + { + Assert(flink->func->cfunc.use_count > 0); + flink->func->cfunc.use_count--; + } + flink->func = func; + func->cfunc.use_count++; + } + + /* + * Create memory context that holds all the SQLFunctionCache data. If we + * return a set, we must keep this in whatever context holds the FmgrInfo + * (anything shorter-lived risks leaving a dangling pointer in flink). But + * in a non-SRF we'll delete it before returning, and there's no need for + * it to outlive the caller's context. + */ + pcontext = func->returnsSet ? finfo->fn_mcxt : CurrentMemoryContext; + fcontext = AllocSetContextCreate(pcontext, + "SQL function execution", + ALLOCSET_DEFAULT_SIZES); + + oldcontext = MemoryContextSwitchTo(fcontext); + + /* + * Create the struct proper, link it to func and fcontext. + */ + fcache = (SQLFunctionCache *) palloc0(sizeof(SQLFunctionCache)); + fcache->func = func; + fcache->fcontext = fcontext; + fcache->lazyEvalOK = lazyEvalOK; + + /* + * If we return a set, we must link the fcache into fn_extra so that we + * can find it again during future calls. But in a non-SRF there is no + * need to link it into fn_extra at all. Not doing so removes the risk of + * having a dangling pointer in a long-lived FmgrInfo. + */ + if (func->returnsSet) + flink->fcache = fcache; + + /* + * We're beginning a new execution of the function, so convert params to + * appropriate format. + */ + postquel_sub_params(fcache, fcinfo); + + MemoryContextSwitchTo(oldcontext); + + return fcache; +} + +/* + * Set up the per-query execution_state records for the next query within + * the SQL function. * - * The input is a List of Lists of parsed and rewritten, but not planned, - * querytrees. The sublist structure denotes the original query boundaries. + * Returns true if successful, false if there are no more queries. */ -static List * -init_execution_state(List *queryTree_list, - SQLFunctionCachePtr fcache, - bool lazyEvalOK) +static bool +init_execution_state(SQLFunctionCachePtr fcache) { - List *eslist = NIL; + CachedPlanSource *plansource; + execution_state *preves = NULL; execution_state *lasttages = NULL; - ListCell *lc1; + ListCell *lc; - foreach(lc1, queryTree_list) + /* + * 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. + */ + if (fcache->cplan) { - List *qtlist = lfirst_node(List, lc1); - execution_state *firstes = NULL; - execution_state *preves = NULL; - ListCell *lc2; + ReleaseCachedPlan(fcache->cplan, fcache->cowner); + fcache->cplan = NULL; + } + fcache->eslist = NULL; - foreach(lc2, qtlist) - { - Query *queryTree = lfirst_node(Query, lc2); - PlannedStmt *stmt; - execution_state *newes; + /* + * Get the next CachedPlanSource, or stop if there are no more. We might + * need to create the next CachedPlanSource; if so, advance + * error_query_index first, so that errors detected in prepare_next_query + * are blamed on the right statement. + */ + if (fcache->next_query_index >= list_length(fcache->func->plansource_list)) + { + if (fcache->next_query_index >= fcache->func->num_queries) + return false; + fcache->error_query_index++; + prepare_next_query(fcache->func); + } + else + fcache->error_query_index++; - /* Plan the query if needed */ - if (queryTree->commandType == CMD_UTILITY) - { - /* Utility commands require no planning. */ - stmt = makeNode(PlannedStmt); - stmt->commandType = CMD_UTILITY; - stmt->canSetTag = queryTree->canSetTag; - stmt->utilityStmt = queryTree->utilityStmt; - stmt->stmt_location = queryTree->stmt_location; - stmt->stmt_len = queryTree->stmt_len; - stmt->queryId = queryTree->queryId; - } - else - stmt = pg_plan_query(queryTree, - fcache->src, - CURSOR_OPT_PARALLEL_OK, - NULL); + plansource = (CachedPlanSource *) list_nth(fcache->func->plansource_list, + fcache->next_query_index); + fcache->next_query_index++; - /* - * Precheck all commands for validity in a function. This should - * generally match the restrictions spi.c applies. - */ - if (stmt->commandType == CMD_UTILITY) - { - if (IsA(stmt->utilityStmt, CopyStmt) && - ((CopyStmt *) stmt->utilityStmt)->filename == NULL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot COPY to/from client in an SQL function"))); + /* + * Generate plans for the query or queries within this CachedPlanSource. + * Register the CachedPlan with the current resource owner. (Saving + * cowner here is mostly paranoia, but this way we needn't assume that + * CurrentResourceOwner will be the same when ShutdownSQLFunction runs.) + */ + fcache->cowner = CurrentResourceOwner; + fcache->cplan = GetCachedPlan(plansource, + fcache->paramLI, + fcache->cowner, + NULL); - if (IsA(stmt->utilityStmt, TransactionStmt)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - /* translator: %s is a SQL statement name */ - errmsg("%s is not allowed in an SQL function", - CreateCommandName(stmt->utilityStmt)))); - } + /* + * Build execution_state list to match the number of contained plans. + */ + foreach(lc, fcache->cplan->stmt_list) + { + PlannedStmt *stmt = lfirst_node(PlannedStmt, lc); + execution_state *newes; - if (fcache->readonly_func && !CommandIsReadOnly(stmt)) + /* + * Precheck all commands for validity in a function. This should + * generally match the restrictions spi.c applies. + */ + if (stmt->commandType == CMD_UTILITY) + { + if (IsA(stmt->utilityStmt, CopyStmt) && + ((CopyStmt *) stmt->utilityStmt)->filename == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot COPY to/from client in an SQL function"))); + + if (IsA(stmt->utilityStmt, TransactionStmt)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */ - errmsg("%s is not allowed in a non-volatile function", - CreateCommandName((Node *) stmt)))); + errmsg("%s is not allowed in an SQL function", + CreateCommandName(stmt->utilityStmt)))); + } - /* OK, build the execution_state for this query */ - newes = (execution_state *) palloc(sizeof(execution_state)); - if (preves) - preves->next = newes; - else - firstes = newes; + if (fcache->func->readonly_func && !CommandIsReadOnly(stmt)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s is a SQL statement name */ + errmsg("%s is not allowed in a non-volatile function", + CreateCommandName((Node *) stmt)))); + + /* OK, build the execution_state for this query */ + newes = (execution_state *) palloc(sizeof(execution_state)); + if (preves) + preves->next = newes; + else + fcache->eslist = newes; - newes->next = NULL; - newes->status = F_EXEC_START; - newes->setsResult = false; /* might change below */ - newes->lazyEval = false; /* might change below */ - newes->stmt = stmt; - newes->qd = NULL; + newes->next = NULL; + newes->status = F_EXEC_START; + newes->setsResult = false; /* might change below */ + newes->lazyEval = false; /* might change below */ + newes->stmt = stmt; + newes->qd = NULL; - if (queryTree->canSetTag) - lasttages = newes; + if (stmt->canSetTag) + lasttages = newes; - preves = newes; - } + preves = newes; + } - eslist = lappend(eslist, firstes); + /* + * If this isn't the last CachedPlanSource, we're done here. Otherwise, + * we need to prepare information about how to return the results. + */ + if (fcache->next_query_index < fcache->func->num_queries) + return true; + + /* + * Construct a JunkFilter we can use to coerce the returned rowtype to the + * desired form, unless the result type is VOID, in which case there's + * nothing to coerce to. (XXX Frequently, the JunkFilter isn't doing + * anything very interesting, but much of this module expects it to be + * there anyway.) + */ + if (fcache->func->rettype != VOIDOID) + { + TupleTableSlot *slot = MakeSingleTupleTableSlot(NULL, + &TTSOpsMinimalTuple); + List *resulttlist; + + /* + * Re-fetch the (possibly modified) output tlist of the final + * statement. By this point, we should have thrown an error if there + * is not one. + */ + resulttlist = get_sql_fn_result_tlist(plansource->query_list); + + /* + * We need to make a copy to ensure that it doesn't disappear + * underneath us due to plancache invalidation. + */ + resulttlist = copyObject(resulttlist); + + /* + * If the result is composite, *and* we are returning the whole tuple + * result, we need to insert nulls for any dropped columns. In the + * single-column-result case, there might be dropped columns within + * the composite column value, but it's not our problem here. There + * should be no resjunk entries in resulttlist, so in the second case + * the JunkFilter is certainly a no-op. + */ + if (fcache->func->rettupdesc && fcache->func->returnsTuple) + fcache->junkFilter = ExecInitJunkFilterConversion(resulttlist, + fcache->func->rettupdesc, + slot); + else + fcache->junkFilter = ExecInitJunkFilter(resulttlist, slot); + } + + if (fcache->func->returnsTuple) + { + /* Make sure output rowtype is properly blessed */ + BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor); + } + else if (fcache->func->returnsSet && type_is_rowtype(fcache->func->rettype)) + { + /* + * Returning rowtype as if it were scalar --- materialize won't work. + * Right now it's sufficient to override any caller preference for + * materialize mode, but this might need more work in future. + */ + fcache->lazyEvalOK = true; } /* @@ -567,69 +841,205 @@ init_execution_state(List *queryTree_list, if (lasttages && fcache->junkFilter) { lasttages->setsResult = true; - if (lazyEvalOK && + if (fcache->lazyEvalOK && lasttages->stmt->commandType == CMD_SELECT && !lasttages->stmt->hasModifyingCTE) fcache->lazyEval = lasttages->lazyEval = true; } - return eslist; + return true; } /* - * Initialize the SQLFunctionCache for a SQL function + * Convert the SQL function's next query from source form (RawStmt or Query) + * into a CachedPlanSource. If it's the last query, also determine whether + * the function returnsTuple. */ static void -init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) +prepare_next_query(SQLFunctionHashEntry *func) { - FmgrInfo *finfo = fcinfo->flinfo; - Oid foid = finfo->fn_oid; - MemoryContext fcontext; + int qindex; + bool islast; + CachedPlanSource *plansource; + List *queryTree_list; MemoryContext oldcontext; + + /* Which query should we process? */ + qindex = list_length(func->plansource_list); + Assert(qindex < func->num_queries); /* else caller error */ + islast = (qindex + 1 >= func->num_queries); + + /* + * Parse and/or rewrite the query, creating a CachedPlanSource that holds + * a copy of the original parsetree. + */ + if (!func->raw_source) + { + /* Source queries are already parse-analyzed */ + Query *parsetree = list_nth_node(Query, func->source_list, qindex); + + plansource = CreateCachedPlanForQuery(parsetree, + func->src, + CreateCommandTag((Node *) parsetree)); + AcquireRewriteLocks(parsetree, true, false); + queryTree_list = pg_rewrite_query(parsetree); + } + else + { + /* Source queries are raw parsetrees */ + RawStmt *parsetree = list_nth_node(RawStmt, func->source_list, qindex); + + plansource = CreateCachedPlan(parsetree, + func->src, + CreateCommandTag(parsetree->stmt)); + queryTree_list = pg_analyze_and_rewrite_withcb(parsetree, + func->src, + (ParserSetupHook) sql_fn_parser_setup, + func->pinfo, + NULL); + } + + /* + * Check that there are no statements we don't want to allow. + */ + check_sql_fn_statement(queryTree_list); + + /* + * If this is the last query, check that the function returns the type it + * claims to. Although in simple cases this was already done when the + * function was defined, we have to recheck because database objects used + * in the function's queries might have changed type. We'd have to + * recheck anyway if the function had any polymorphic arguments. Moreover, + * check_sql_stmt_retval takes care of injecting any required column type + * coercions. (But we don't ask it to insert nulls for dropped columns; + * the junkfilter handles that.) + * + * Note: we set func->returnsTuple according to whether we are returning + * the whole tuple result or just a single column. In the latter case we + * clear returnsTuple because we need not act different from the scalar + * result case, even if it's a rowtype column. (However, we have to force + * lazy eval mode in that case; otherwise we'd need extra code to expand + * the rowtype column into multiple columns, since we have no way to + * notify the caller that it should do that.) + */ + if (islast) + func->returnsTuple = check_sql_stmt_retval(queryTree_list, + func->rettype, + func->rettupdesc, + func->prokind, + false); + + /* + * Now that check_sql_stmt_retval has done its thing, we can complete plan + * cache entry creation. + */ + CompleteCachedPlan(plansource, + queryTree_list, + NULL, + NULL, + 0, + (ParserSetupHook) sql_fn_parser_setup, + func->pinfo, + CURSOR_OPT_PARALLEL_OK | CURSOR_OPT_NO_SCROLL, + false); + + /* + * Install post-rewrite hook. Its arg is the hash entry if this is the + * last statement, else NULL. + */ + SetPostRewriteHook(plansource, + sql_postrewrite_callback, + islast ? func : NULL); + + /* + * While the CachedPlanSources can take care of themselves, our List + * pointing to them had better be in the hcontext. + */ + oldcontext = MemoryContextSwitchTo(func->hcontext); + func->plansource_list = lappend(func->plansource_list, plansource); + MemoryContextSwitchTo(oldcontext); + + /* + * As soon as we've linked the CachedPlanSource into the list, mark it as + * "saved". + */ + SaveCachedPlan(plansource); + + /* + * Finally, if this was the last statement, we can flush the pcontext with + * the original query trees; they're all safely copied into + * CachedPlanSources now. + */ + if (islast) + { + func->source_list = NIL; /* avoid dangling pointer */ + MemoryContextDelete(func->pcontext); + func->pcontext = NULL; + } +} + +/* + * Fill a new SQLFunctionHashEntry. + * + * The passed-in "cfunc" struct is expected to be zeroes, except + * for the CachedFunction fields, which we don't touch here. + * + * We expect to be called in a short-lived memory context (typically a + * query's per-tuple context). Data that is to be part of the hash entry + * must be copied into the hcontext or pcontext as appropriate. + */ +static void +sql_compile_callback(FunctionCallInfo fcinfo, + HeapTuple procedureTuple, + const CachedFunctionHashKey *hashkey, + CachedFunction *cfunc, + bool forValidator) +{ + SQLFunctionHashEntry *func = (SQLFunctionHashEntry *) cfunc; + Form_pg_proc procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); + ErrorContextCallback comperrcontext; + MemoryContext hcontext; + MemoryContext pcontext; + MemoryContext oldcontext = CurrentMemoryContext; Oid rettype; TupleDesc rettupdesc; - HeapTuple procedureTuple; - Form_pg_proc procedureStruct; - SQLFunctionCachePtr fcache; - List *queryTree_list; - List *resulttlist; - ListCell *lc; Datum tmp; bool isNull; + List *source_list; /* - * Create memory context that holds all the SQLFunctionCache data. It - * must be a child of whatever context holds the FmgrInfo. + * Setup error traceback support for ereport() during compile. (This is + * mainly useful for reporting parse errors from pg_parse_query.) */ - fcontext = AllocSetContextCreate(finfo->fn_mcxt, - "SQL function", - ALLOCSET_DEFAULT_SIZES); - - oldcontext = MemoryContextSwitchTo(fcontext); + comperrcontext.callback = sql_compile_error_callback; + comperrcontext.arg = func; + comperrcontext.previous = error_context_stack; + error_context_stack = &comperrcontext; /* - * Create the struct proper, link it to fcontext and fn_extra. Once this - * is done, we'll be able to recover the memory after failure, even if the - * FmgrInfo is long-lived. + * Create the hash entry's memory context. For now it's a child of the + * caller's context, so that it will go away if we fail partway through. */ - fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache)); - fcache->fcontext = fcontext; - finfo->fn_extra = fcache; + hcontext = AllocSetContextCreate(CurrentMemoryContext, + "SQL function", + ALLOCSET_SMALL_SIZES); /* - * get the procedure tuple corresponding to the given function Oid + * Create the not-as-long-lived pcontext. We make this a child of + * hcontext so that it doesn't require separate deletion. */ - procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(foid)); - if (!HeapTupleIsValid(procedureTuple)) - elog(ERROR, "cache lookup failed for function %u", foid); - procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); + pcontext = AllocSetContextCreate(hcontext, + "SQL function parse trees", + ALLOCSET_SMALL_SIZES); + func->pcontext = pcontext; /* * copy function name immediately for use by error reporting callback, and * for use as memory context identifier */ - fcache->fname = pstrdup(NameStr(procedureStruct->proname)); - MemoryContextSetIdentifier(fcontext, fcache->fname); + func->fname = MemoryContextStrdup(hcontext, + NameStr(procedureStruct->proname)); + MemoryContextSetIdentifier(hcontext, func->fname); /* * Resolve any polymorphism, obtaining the actual result type, and the @@ -637,176 +1047,158 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) */ (void) get_call_result_type(fcinfo, &rettype, &rettupdesc); - fcache->rettype = rettype; + func->rettype = rettype; + if (rettupdesc) + { + MemoryContextSwitchTo(hcontext); + func->rettupdesc = CreateTupleDescCopy(rettupdesc); + MemoryContextSwitchTo(oldcontext); + } /* Fetch the typlen and byval info for the result type */ - get_typlenbyval(rettype, &fcache->typlen, &fcache->typbyval); + get_typlenbyval(rettype, &func->typlen, &func->typbyval); /* Remember whether we're returning setof something */ - fcache->returnsSet = procedureStruct->proretset; + func->returnsSet = procedureStruct->proretset; /* Remember if function is STABLE/IMMUTABLE */ - fcache->readonly_func = + func->readonly_func = (procedureStruct->provolatile != PROVOLATILE_VOLATILE); + /* Remember routine kind */ + func->prokind = procedureStruct->prokind; + /* * We need the actual argument types to pass to the parser. Also make * sure that parameter symbols are considered to have the function's * resolved input collation. */ - fcache->pinfo = prepare_sql_fn_parse_info(procedureTuple, - finfo->fn_expr, - collation); + MemoryContextSwitchTo(hcontext); + func->pinfo = prepare_sql_fn_parse_info(procedureTuple, + fcinfo->flinfo->fn_expr, + PG_GET_COLLATION()); + MemoryContextSwitchTo(oldcontext); /* * And of course we need the function body text. */ tmp = SysCacheGetAttrNotNull(PROCOID, procedureTuple, Anum_pg_proc_prosrc); - fcache->src = TextDatumGetCString(tmp); + func->src = MemoryContextStrdup(hcontext, + TextDatumGetCString(tmp)); /* If we have prosqlbody, pay attention to that not prosrc. */ tmp = SysCacheGetAttr(PROCOID, procedureTuple, Anum_pg_proc_prosqlbody, &isNull); - - /* - * Parse and rewrite the queries in the function text. Use sublists to - * keep track of the original query boundaries. - * - * Note: since parsing and planning is done in fcontext, we will generate - * a lot of cruft that lives as long as the fcache does. This is annoying - * but we'll not worry about it until the module is rewritten to use - * plancache.c. - */ - queryTree_list = NIL; if (!isNull) { + /* Source queries are already parse-analyzed */ Node *n; - List *stored_query_list; n = stringToNode(TextDatumGetCString(tmp)); if (IsA(n, List)) - stored_query_list = linitial_node(List, castNode(List, n)); + source_list = linitial_node(List, castNode(List, n)); else - stored_query_list = list_make1(n); - - foreach(lc, stored_query_list) - { - Query *parsetree = lfirst_node(Query, lc); - List *queryTree_sublist; - - AcquireRewriteLocks(parsetree, true, false); - queryTree_sublist = pg_rewrite_query(parsetree); - queryTree_list = lappend(queryTree_list, queryTree_sublist); - } + source_list = list_make1(n); + func->raw_source = false; } else { - List *raw_parsetree_list; - - raw_parsetree_list = pg_parse_query(fcache->src); - - foreach(lc, raw_parsetree_list) - { - RawStmt *parsetree = lfirst_node(RawStmt, lc); - List *queryTree_sublist; - - queryTree_sublist = pg_analyze_and_rewrite_withcb(parsetree, - fcache->src, - (ParserSetupHook) sql_fn_parser_setup, - fcache->pinfo, - NULL); - queryTree_list = lappend(queryTree_list, queryTree_sublist); - } + /* Source queries are raw parsetrees */ + source_list = pg_parse_query(func->src); + func->raw_source = true; } /* - * Check that there are no statements we don't want to allow. + * Note: we must save the number of queries so that we'll still remember + * how many there are after we discard source_list. */ - check_sql_fn_statements(queryTree_list); + func->num_queries = list_length(source_list); - /* - * Check that the function returns the type it claims to. Although in - * simple cases this was already done when the function was defined, we - * have to recheck because database objects used in the function's queries - * might have changed type. We'd have to recheck anyway if the function - * had any polymorphic arguments. Moreover, check_sql_fn_retval takes - * care of injecting any required column type coercions. (But we don't - * ask it to insert nulls for dropped columns; the junkfilter handles - * that.) - * - * Note: we set fcache->returnsTuple according to whether we are returning - * the whole tuple result or just a single column. In the latter case we - * clear returnsTuple because we need not act different from the scalar - * result case, even if it's a rowtype column. (However, we have to force - * lazy eval mode in that case; otherwise we'd need extra code to expand - * the rowtype column into multiple columns, since we have no way to - * notify the caller that it should do that.) - */ - fcache->returnsTuple = check_sql_fn_retval(queryTree_list, - rettype, - rettupdesc, - procedureStruct->prokind, - false, - &resulttlist); + /* Save the source trees in pcontext for now. */ + MemoryContextSwitchTo(pcontext); + func->source_list = copyObject(source_list); + MemoryContextSwitchTo(oldcontext); /* - * Construct a JunkFilter we can use to coerce the returned rowtype to the - * desired form, unless the result type is VOID, in which case there's - * nothing to coerce to. (XXX Frequently, the JunkFilter isn't doing - * anything very interesting, but much of this module expects it to be - * there anyway.) + * We now have a fully valid hash entry, so reparent hcontext under + * CacheMemoryContext to make all the subsidiary data long-lived, and only + * then install the hcontext link so that sql_delete_callback will know to + * delete it. */ - if (rettype != VOIDOID) - { - TupleTableSlot *slot = MakeSingleTupleTableSlot(NULL, - &TTSOpsMinimalTuple); + MemoryContextSetParent(hcontext, CacheMemoryContext); + func->hcontext = hcontext; - /* - * If the result is composite, *and* we are returning the whole tuple - * result, we need to insert nulls for any dropped columns. In the - * single-column-result case, there might be dropped columns within - * the composite column value, but it's not our problem here. There - * should be no resjunk entries in resulttlist, so in the second case - * the JunkFilter is certainly a no-op. - */ - if (rettupdesc && fcache->returnsTuple) - fcache->junkFilter = ExecInitJunkFilterConversion(resulttlist, - rettupdesc, - slot); - else - fcache->junkFilter = ExecInitJunkFilter(resulttlist, slot); - } + error_context_stack = comperrcontext.previous; +} - if (fcache->returnsTuple) - { - /* Make sure output rowtype is properly blessed */ - BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor); - } - else if (fcache->returnsSet && type_is_rowtype(fcache->rettype)) +/* + * Deletion callback used by funccache.c. + * + * Free any free-able subsidiary data of cfunc, but not the + * struct CachedFunction itself. + */ +static void +sql_delete_callback(CachedFunction *cfunc) +{ + SQLFunctionHashEntry *func = (SQLFunctionHashEntry *) cfunc; + ListCell *lc; + + /* Release the CachedPlanSources */ + foreach(lc, func->plansource_list) { - /* - * Returning rowtype as if it were scalar --- materialize won't work. - * Right now it's sufficient to override any caller preference for - * materialize mode, but to add more smarts in init_execution_state - * about this, we'd probably need a three-way flag instead of bool. - */ - lazyEvalOK = true; - } + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); - /* Finally, plan the queries */ - fcache->func_state = init_execution_state(queryTree_list, - fcache, - lazyEvalOK); + DropCachedPlan(plansource); + } + func->plansource_list = NIL; - /* Mark fcache with time of creation to show it's valid */ - fcache->lxid = MyProc->vxid.lxid; - fcache->subxid = GetCurrentSubTransactionId(); + /* + * If we have an hcontext, free it, thereby getting rid of all subsidiary + * data. (If we still have a pcontext, this gets rid of that too.) + */ + if (func->hcontext) + MemoryContextDelete(func->hcontext); + func->hcontext = NULL; +} - ReleaseSysCache(procedureTuple); +/* + * Post-rewrite callback used by plancache.c. + * + * This must match the processing that prepare_next_query() does between + * rewriting and calling CompleteCachedPlan(). + */ +static void +sql_postrewrite_callback(List *querytree_list, void *arg) +{ + /* + * Check that there are no statements we don't want to allow. (Presently, + * there's no real point in this because the result can't change from what + * we saw originally. But it's cheap and maybe someday it will matter.) + */ + check_sql_fn_statement(querytree_list); - MemoryContextSwitchTo(oldcontext); + /* + * If this is the last query, we must re-do what check_sql_stmt_retval did + * to its targetlist. Also check that returnsTuple didn't change (it + * probably cannot, but be cautious). + */ + if (arg != NULL) + { + SQLFunctionHashEntry *func = (SQLFunctionHashEntry *) arg; + bool returnsTuple; + + returnsTuple = check_sql_stmt_retval(querytree_list, + func->rettype, + func->rettupdesc, + func->prokind, + false); + if (returnsTuple != func->returnsTuple) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cached plan must not change result type"))); + } } /* Start up execution of one execution_state node */ @@ -841,7 +1233,7 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) es->qd = CreateQueryDesc(es->stmt, NULL, - fcache->src, + fcache->func->src, GetActiveSnapshot(), InvalidSnapshot, dest, @@ -882,7 +1274,7 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache) if (es->qd->operation == CMD_UTILITY) { ProcessUtility(es->qd->plannedstmt, - fcache->src, + fcache->func->src, true, /* protect function cache's parsetree */ PROCESS_UTILITY_QUERY, es->qd->params, @@ -938,7 +1330,7 @@ postquel_sub_params(SQLFunctionCachePtr fcache, if (nargs > 0) { ParamListInfo paramLI; - Oid *argtypes = fcache->pinfo->argtypes; + Oid *argtypes = fcache->func->pinfo->argtypes; if (fcache->paramLI == NULL) { @@ -971,7 +1363,8 @@ postquel_sub_params(SQLFunctionCachePtr fcache, prm->value = MakeExpandedObjectReadOnly(fcinfo->args[i].value, prm->isnull, get_typlen(argtypes[i])); - prm->pflags = 0; + /* Allow the value to be substituted into custom plans */ + prm->pflags = PARAM_FLAG_CONST; prm->ptype = argtypes[i]; } } @@ -1001,7 +1394,7 @@ postquel_get_single_result(TupleTableSlot *slot, */ oldcontext = MemoryContextSwitchTo(resultcontext); - if (fcache->returnsTuple) + if (fcache->func->returnsTuple) { /* We must return the whole tuple as a Datum. */ fcinfo->isnull = false; @@ -1016,7 +1409,7 @@ postquel_get_single_result(TupleTableSlot *slot, value = slot_getattr(slot, 1, &(fcinfo->isnull)); if (!fcinfo->isnull) - value = datumCopy(value, fcache->typbyval, fcache->typlen); + value = datumCopy(value, fcache->func->typbyval, fcache->func->typlen); } MemoryContextSwitchTo(oldcontext); @@ -1031,25 +1424,16 @@ Datum fmgr_sql(PG_FUNCTION_ARGS) { SQLFunctionCachePtr fcache; + SQLFunctionLink *flink; ErrorContextCallback sqlerrcontext; + MemoryContext tscontext; MemoryContext oldcontext; bool randomAccess; bool lazyEvalOK; - bool is_first; bool pushed_snapshot; execution_state *es; TupleTableSlot *slot; Datum result; - List *eslist; - ListCell *eslc; - - /* - * Setup error traceback support for ereport() - */ - sqlerrcontext.callback = sql_exec_error_callback; - sqlerrcontext.arg = fcinfo->flinfo; - sqlerrcontext.previous = error_context_stack; - error_context_stack = &sqlerrcontext; /* Check call context */ if (fcinfo->flinfo->fn_retset) @@ -1070,80 +1454,63 @@ fmgr_sql(PG_FUNCTION_ARGS) errmsg("set-valued function called in context that cannot accept a set"))); randomAccess = rsi->allowedModes & SFRM_Materialize_Random; lazyEvalOK = !(rsi->allowedModes & SFRM_Materialize_Preferred); + /* tuplestore must have query lifespan */ + tscontext = rsi->econtext->ecxt_per_query_memory; } else { randomAccess = false; lazyEvalOK = true; + /* tuplestore needn't outlive caller context */ + tscontext = CurrentMemoryContext; } /* - * Initialize fcache (build plans) if first time through; or re-initialize - * if the cache is stale. + * Initialize fcache if starting a fresh execution. */ - fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra; + fcache = init_sql_fcache(fcinfo, lazyEvalOK); + /* init_sql_fcache also ensures we have a SQLFunctionLink */ + flink = fcinfo->flinfo->fn_extra; - if (fcache != NULL) - { - if (fcache->lxid != MyProc->vxid.lxid || - !SubTransactionIsActive(fcache->subxid)) - { - /* It's stale; unlink and delete */ - fcinfo->flinfo->fn_extra = NULL; - MemoryContextDelete(fcache->fcontext); - fcache = NULL; - } - } + /* + * Now we can set up error traceback support for ereport() + */ + sqlerrcontext.callback = sql_exec_error_callback; + sqlerrcontext.arg = fcache; + sqlerrcontext.previous = error_context_stack; + error_context_stack = &sqlerrcontext; - if (fcache == NULL) - { - init_sql_fcache(fcinfo, PG_GET_COLLATION(), lazyEvalOK); - fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra; - } + /* + * 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); /* - * Switch to context in which the fcache lives. This ensures that our - * tuplestore etc will have sufficient lifetime. The sub-executor is + * 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 represents more memory leakage, but - * it's not entirely clear where to keep stuff instead.) + * 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); + MemoryContextSwitchTo(fcache->fcontext); /* - * Find first unfinished query in function, and note whether it's the - * first query. + * Find first unfinished execution_state. If none, advance to the next + * query in function. */ - eslist = fcache->func_state; - es = NULL; - is_first = true; - foreach(eslc, eslist) + do { - es = (execution_state *) lfirst(eslc); - + es = fcache->eslist; while (es && es->status == F_EXEC_DONE) - { - is_first = false; es = es->next; - } - if (es) break; - } - - /* - * Convert params to appropriate format if starting a fresh execution. (If - * continuing execution, we can re-use prior params.) - */ - if (is_first && es && es->status == F_EXEC_START) - postquel_sub_params(fcache, fcinfo); - - /* - * Build tuplestore to hold results, if we don't have one already. Note - * it's in the query-lifespan context. - */ - if (!fcache->tstore) - fcache->tstore = tuplestore_begin_heap(randomAccess, false, work_mem); + } while (init_execution_state(fcache)); /* * Execute each command in the function one after another until we either @@ -1176,7 +1543,7 @@ fmgr_sql(PG_FUNCTION_ARGS) * visible. Take a new snapshot if we don't have one yet, * otherwise just bump the command ID in the existing snapshot. */ - if (!fcache->readonly_func) + if (!fcache->func->readonly_func) { CommandCounterIncrement(); if (!pushed_snapshot) @@ -1190,7 +1557,7 @@ fmgr_sql(PG_FUNCTION_ARGS) postquel_start(es, fcache); } - else if (!fcache->readonly_func && !pushed_snapshot) + else if (!fcache->func->readonly_func && !pushed_snapshot) { /* Re-establish active snapshot when re-entering function */ PushActiveSnapshot(es->qd->snapshot); @@ -1207,7 +1574,7 @@ fmgr_sql(PG_FUNCTION_ARGS) * set, we can shut it down anyway because it must be a SELECT and we * don't care about fetching any more result rows. */ - if (completed || !fcache->returnsSet) + if (completed || !fcache->func->returnsSet) postquel_end(es); /* @@ -1223,17 +1590,11 @@ fmgr_sql(PG_FUNCTION_ARGS) break; /* - * Advance to next execution_state, which might be in the next list. + * Advance to next execution_state, and perhaps next query. */ es = es->next; while (!es) { - eslc = lnext(eslist, eslc); - if (!eslc) - break; /* end of function */ - - es = (execution_state *) lfirst(eslc); - /* * Flush the current snapshot so that we will take a new one for * the new query list. This ensures that new snaps are taken at @@ -1245,13 +1606,18 @@ fmgr_sql(PG_FUNCTION_ARGS) PopActiveSnapshot(); pushed_snapshot = false; } + + if (!init_execution_state(fcache)) + break; /* end of function */ + + es = fcache->eslist; } } /* * The tuplestore now contains whatever row(s) we are supposed to return. */ - if (fcache->returnsSet) + if (fcache->func->returnsSet) { ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; @@ -1287,7 +1653,7 @@ fmgr_sql(PG_FUNCTION_ARGS) { RegisterExprContextCallback(rsi->econtext, ShutdownSQLFunction, - PointerGetDatum(fcache)); + PointerGetDatum(flink)); fcache->shutdown_reg = true; } } @@ -1311,7 +1677,7 @@ fmgr_sql(PG_FUNCTION_ARGS) { UnregisterExprContextCallback(rsi->econtext, ShutdownSQLFunction, - PointerGetDatum(fcache)); + PointerGetDatum(flink)); fcache->shutdown_reg = false; } } @@ -1327,7 +1693,12 @@ 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; @@ -1337,7 +1708,7 @@ fmgr_sql(PG_FUNCTION_ARGS) { UnregisterExprContextCallback(rsi->econtext, ShutdownSQLFunction, - PointerGetDatum(fcache)); + PointerGetDatum(flink)); fcache->shutdown_reg = false; } } @@ -1363,7 +1734,7 @@ fmgr_sql(PG_FUNCTION_ARGS) else { /* Should only get here for VOID functions and procedures */ - Assert(fcache->rettype == VOIDOID); + Assert(fcache->func->rettype == VOIDOID); fcinfo->isnull = true; result = (Datum) 0; } @@ -1376,154 +1747,167 @@ fmgr_sql(PG_FUNCTION_ARGS) if (pushed_snapshot) PopActiveSnapshot(); + MemoryContextSwitchTo(oldcontext); + /* - * If we've gone through every command in the function, we are done. Reset - * the execution states to start over again on next call. + * If we've gone through every command in the function, we are done. + * Release the cache to start over again on next call. */ if (es == NULL) { - foreach(eslc, fcache->func_state) - { - es = (execution_state *) lfirst(eslc); - while (es) - { - es->status = F_EXEC_START; - es = es->next; - } - } + if (fcache->tstore) + tuplestore_end(fcache->tstore); + Assert(fcache->cplan == NULL); + flink->fcache = NULL; + MemoryContextDelete(fcache->fcontext); } error_context_stack = sqlerrcontext.previous; - MemoryContextSwitchTo(oldcontext); - return result; } /* - * error context callback to let us supply a call-stack traceback + * error context callback to let us supply a traceback during compile */ static void -sql_exec_error_callback(void *arg) +sql_compile_error_callback(void *arg) { - FmgrInfo *flinfo = (FmgrInfo *) arg; - SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) flinfo->fn_extra; + SQLFunctionHashEntry *func = (SQLFunctionHashEntry *) arg; int syntaxerrposition; /* - * We can do nothing useful if init_sql_fcache() didn't get as far as - * saving the function name + * We can do nothing useful if sql_compile_callback() didn't get as far as + * copying the function name */ - if (fcache == NULL || fcache->fname == NULL) + if (func->fname == NULL) return; /* * If there is a syntax error position, convert to internal syntax error */ syntaxerrposition = geterrposition(); - if (syntaxerrposition > 0 && fcache->src != NULL) + if (syntaxerrposition > 0 && func->src != NULL) { errposition(0); internalerrposition(syntaxerrposition); - internalerrquery(fcache->src); + internalerrquery(func->src); } /* - * Try to determine where in the function we failed. If there is a query - * with non-null QueryDesc, finger it. (We check this rather than looking - * for F_EXEC_RUN state, so that errors during ExecutorStart or - * ExecutorEnd are blamed on the appropriate query; see postquel_start and - * postquel_end.) + * sql_compile_callback() doesn't do any per-query processing, so just + * report the context as "during startup". */ - if (fcache->func_state) - { - execution_state *es; - int query_num; - ListCell *lc; + errcontext("SQL function \"%s\" during startup", func->fname); +} - es = NULL; - query_num = 1; - foreach(lc, fcache->func_state) - { - es = (execution_state *) lfirst(lc); - while (es) - { - if (es->qd) - { - errcontext("SQL function \"%s\" statement %d", - fcache->fname, query_num); - break; - } - es = es->next; - } - if (es) - break; - query_num++; - } - if (es == NULL) - { - /* - * couldn't identify a running query; might be function entry, - * function exit, or between queries. - */ - errcontext("SQL function \"%s\"", fcache->fname); - } - } - else +/* + * error context callback to let us supply a call-stack traceback at runtime + */ +static void +sql_exec_error_callback(void *arg) +{ + SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) arg; + int syntaxerrposition; + + /* + * If there is a syntax error position, convert to internal syntax error + */ + syntaxerrposition = geterrposition(); + if (syntaxerrposition > 0 && fcache->func->src != NULL) { - /* - * Assume we failed during init_sql_fcache(). (It's possible that the - * function actually has an empty body, but in that case we may as - * well report all errors as being "during startup".) - */ - errcontext("SQL function \"%s\" during startup", fcache->fname); + errposition(0); + internalerrposition(syntaxerrposition); + internalerrquery(fcache->func->src); } + + /* + * If we failed while executing an identifiable query within the function, + * report that. Otherwise say it was "during startup". + */ + if (fcache->error_query_index > 0) + errcontext("SQL function \"%s\" statement %d", + fcache->func->fname, fcache->error_query_index); + else + errcontext("SQL function \"%s\" during startup", fcache->func->fname); } /* - * callback function in case a function-returning-set needs to be shut down - * before it has been run to completion + * ExprContext callback function + * + * We register this in the active ExprContext while a set-returning SQL + * function is running, in case the function needs to be shut down before it + * has been run to completion. Note that this will not be called during an + * error abort, but we don't need it because transaction abort will take care + * of releasing executor resources. */ static void ShutdownSQLFunction(Datum arg) { - SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) DatumGetPointer(arg); - execution_state *es; - ListCell *lc; + SQLFunctionLink *flink = (SQLFunctionLink *) DatumGetPointer(arg); + SQLFunctionCachePtr fcache = flink->fcache; - foreach(lc, fcache->func_state) + if (fcache != NULL) { - es = (execution_state *) lfirst(lc); + execution_state *es; + + /* Make sure we don't somehow try to do this twice */ + flink->fcache = NULL; + + es = fcache->eslist; while (es) { /* Shut down anything still running */ if (es->status == F_EXEC_RUN) { /* Re-establish active snapshot for any called functions */ - if (!fcache->readonly_func) + if (!fcache->func->readonly_func) PushActiveSnapshot(es->qd->snapshot); postquel_end(es); - if (!fcache->readonly_func) + if (!fcache->func->readonly_func) PopActiveSnapshot(); } - - /* Reset states to START in case we're called again */ - es->status = F_EXEC_START; es = es->next; } - } - /* Release tuplestore if we have one */ - if (fcache->tstore) - tuplestore_end(fcache->tstore); - fcache->tstore = NULL; + /* Release tuplestore if we have one */ + if (fcache->tstore) + tuplestore_end(fcache->tstore); + /* Release CachedPlan if we have one */ + if (fcache->cplan) + ReleaseCachedPlan(fcache->cplan, fcache->cowner); + + /* Release the cache */ + MemoryContextDelete(fcache->fcontext); + } /* execUtils will deregister the callback... */ - fcache->shutdown_reg = false; +} + +/* + * MemoryContext callback function + * + * We register this in the memory context that contains a SQLFunctionLink + * struct. When the memory context is reset or deleted, we release the + * reference count (if any) that the link holds on the long-lived hash entry. + * Note that this will happen even during error aborts. + */ +static void +RemoveSQLFunctionLink(void *arg) +{ + SQLFunctionLink *flink = (SQLFunctionLink *) arg; + + if (flink->func != NULL) + { + Assert(flink->func->cfunc.use_count > 0); + flink->func->cfunc.use_count--; + /* This should be unnecessary, but let's just be sure: */ + flink->func = NULL; + } } /* @@ -1541,29 +1925,39 @@ check_sql_fn_statements(List *queryTreeLists) foreach(lc, queryTreeLists) { List *sublist = lfirst_node(List, lc); - ListCell *lc2; - foreach(lc2, sublist) - { - Query *query = lfirst_node(Query, lc2); + check_sql_fn_statement(sublist); + } +} - /* - * Disallow calling procedures with output arguments. The current - * implementation would just throw the output values away, unless - * the statement is the last one. Per SQL standard, we should - * assign the output values by name. By disallowing this here, we - * preserve an opportunity for future improvement. - */ - if (query->commandType == CMD_UTILITY && - IsA(query->utilityStmt, CallStmt)) - { - CallStmt *stmt = (CallStmt *) query->utilityStmt; +/* + * As above, for a single sublist of Queries. + */ +static void +check_sql_fn_statement(List *queryTreeList) +{ + ListCell *lc; - if (stmt->outargs != NIL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("calling procedures with output arguments is not supported in SQL functions"))); - } + foreach(lc, queryTreeList) + { + Query *query = lfirst_node(Query, lc); + + /* + * Disallow calling procedures with output arguments. The current + * implementation would just throw the output values away, unless the + * statement is the last one. Per SQL standard, we should assign the + * output values by name. By disallowing this here, we preserve an + * opportunity for future improvement. + */ + if (query->commandType == CMD_UTILITY && + IsA(query->utilityStmt, CallStmt)) + { + CallStmt *stmt = (CallStmt *) query->utilityStmt; + + if (stmt->outargs != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("calling procedures with output arguments is not supported in SQL functions"))); } } } @@ -1602,17 +1996,45 @@ check_sql_fn_statements(List *queryTreeLists) * In addition to coercing individual output columns, we can modify the * output to include dummy NULL columns for any dropped columns appearing * in rettupdesc. This is done only if the caller asks for it. - * - * If resultTargetList isn't NULL, then *resultTargetList is set to the - * targetlist that defines the final statement's result. Exception: if the - * function is defined to return VOID then *resultTargetList is set to NIL. */ bool check_sql_fn_retval(List *queryTreeLists, Oid rettype, TupleDesc rettupdesc, char prokind, - bool insertDroppedCols, - List **resultTargetList) + bool insertDroppedCols) +{ + List *queryTreeList; + + /* + * We consider only the last sublist of Query nodes, so that only the last + * original statement is a candidate to produce the result. This is a + * change from pre-v18 versions, which would back up to the last statement + * that includes a canSetTag query, thus ignoring any ending statement(s) + * that rewrite to DO INSTEAD NOTHING. That behavior was undocumented and + * there seems no good reason for it, except that it was an artifact of + * the original coding. + * + * If the function body is completely empty, handle that the same as if + * the last query had rewritten to nothing. + */ + if (queryTreeLists != NIL) + queryTreeList = llast_node(List, queryTreeLists); + else + queryTreeList = NIL; + + return check_sql_stmt_retval(queryTreeList, + rettype, rettupdesc, + prokind, insertDroppedCols); +} + +/* + * As for check_sql_fn_retval, but we are given just the last query's + * rewritten-queries list. + */ +static bool +check_sql_stmt_retval(List *queryTreeList, + Oid rettype, TupleDesc rettupdesc, + char prokind, bool insertDroppedCols) { bool is_tuple_result = false; Query *parse; @@ -1625,9 +2047,6 @@ check_sql_fn_retval(List *queryTreeLists, bool upper_tlist_nontrivial = false; ListCell *lc; - if (resultTargetList) - *resultTargetList = NIL; /* initialize in case of VOID result */ - /* * If it's declared to return VOID, we don't care what's in the function. * (This takes care of procedures with no output parameters, as well.) @@ -1636,30 +2055,20 @@ check_sql_fn_retval(List *queryTreeLists, return false; /* - * Find the last canSetTag query in the function body (which is presented - * to us as a list of sublists of Query nodes). This isn't necessarily - * the last parsetree, because rule rewriting can insert queries after - * what the user wrote. Note that it might not even be in the last - * sublist, for example if the last query rewrites to DO INSTEAD NOTHING. - * (It might not be unreasonable to throw an error in such a case, but - * this is the historical behavior and it doesn't seem worth changing.) + * Find the last canSetTag query in the list of Query nodes. This isn't + * necessarily the last parsetree, because rule rewriting can insert + * queries after what the user wrote. */ parse = NULL; parse_cell = NULL; - foreach(lc, queryTreeLists) + foreach(lc, queryTreeList) { - List *sublist = lfirst_node(List, lc); - ListCell *lc2; + Query *q = lfirst_node(Query, lc); - foreach(lc2, sublist) + if (q->canSetTag) { - Query *q = lfirst_node(Query, lc2); - - if (q->canSetTag) - { - parse = q; - parse_cell = lc2; - } + parse = q; + parse_cell = lc; } } @@ -1812,12 +2221,7 @@ check_sql_fn_retval(List *queryTreeLists, * further checking. Assume we're returning the whole tuple. */ if (rettupdesc == NULL) - { - /* Return tlist if requested */ - if (resultTargetList) - *resultTargetList = tlist; return true; - } /* * Verify that the targetlist matches the return tuple type. We scan @@ -1984,10 +2388,6 @@ tlist_coercion_finished: lfirst(parse_cell) = newquery; } - /* Return tlist (possibly modified) if requested */ - if (resultTargetList) - *resultTargetList = upper_tlist; - return is_tuple_result; } @@ -2063,6 +2463,37 @@ coerce_fn_result_column(TargetEntry *src_tle, return true; } +/* + * Extract the targetlist of the last canSetTag query in the given list + * of parsed-and-rewritten Queries. Returns NIL if there is none. + */ +static List * +get_sql_fn_result_tlist(List *queryTreeList) +{ + Query *parse = NULL; + ListCell *lc; + + foreach(lc, queryTreeList) + { + Query *q = lfirst_node(Query, lc); + + if (q->canSetTag) + parse = q; + } + if (parse && + parse->commandType == CMD_SELECT) + return parse->targetList; + else if (parse && + (parse->commandType == CMD_INSERT || + parse->commandType == CMD_UPDATE || + parse->commandType == CMD_DELETE || + parse->commandType == CMD_MERGE) && + parse->returningList) + return parse->returningList; + else + return NIL; +} + /* * CreateSQLFunctionDestReceiver -- create a suitable DestReceiver object |