aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/functions.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor/functions.c')
-rw-r--r--src/backend/executor/functions.c1391
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