diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 35d34f224ef..8074f66417d 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -234,21 +234,6 @@ CALL clean_emp();
whereas returning void is a PostgreSQL extension.
-
-
- The entire body of an SQL function is parsed before any of it is
- executed. While an SQL function can contain commands that alter
- the system catalogs (e.g., CREATE TABLE), the effects
- of such commands will not be visible during parse analysis of
- later commands in the function. Thus, for example,
- CREATE TABLE foo (...); INSERT INTO foo VALUES(...);
- will not work as desired if packaged up into a single SQL function,
- since foo won't exist yet when the INSERT
- command is parsed. It's recommended to use PL/pgSQL
- instead of an SQL function in this type of situation.
-
-
-
The syntax of the CREATE FUNCTION command requires
the function body to be written as a string constant. It is usually
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index fe0490259e9..880b597fb3a 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -960,7 +960,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
(void) check_sql_fn_retval(querytree_list,
rettype, rettupdesc,
proc->prokind,
- false, NULL);
+ false);
}
error_context_stack = sqlerrcontext.previous;
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.
- *
- * The input is a List of Lists of parsed and rewritten, but not planned,
- * querytrees. The sublist structure denotes the original query boundaries.
+ * Initialize the SQLFunctionCache for a SQL function
*/
-static List *
-init_execution_state(List *queryTree_list,
- SQLFunctionCachePtr fcache,
- bool lazyEvalOK)
+static SQLFunctionCache *
+init_sql_fcache(FunctionCallInfo fcinfo, bool lazyEvalOK)
{
- List *eslist = NIL;
- execution_state *lasttages = NULL;
- ListCell *lc1;
+ FmgrInfo *finfo = fcinfo->flinfo;
+ SQLFunctionHashEntry *func;
+ SQLFunctionCache *fcache;
+ SQLFunctionLink *flink;
+ MemoryContext pcontext;
+ MemoryContext fcontext;
+ MemoryContext oldcontext;
- foreach(lc1, queryTree_list)
+ /*
+ * 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)
{
- List *qtlist = lfirst_node(List, lc1);
- execution_state *firstes = NULL;
- execution_state *preves = NULL;
- ListCell *lc2;
+ 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;
+ }
- foreach(lc2, qtlist)
+ /*
+ * 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)
{
- Query *queryTree = lfirst_node(Query, lc2);
- PlannedStmt *stmt;
- execution_state *newes;
+ Assert(flink->func->cfunc.use_count > 0);
+ flink->func->cfunc.use_count--;
+ }
+ flink->func = func;
+ func->cfunc.use_count++;
+ }
- /* 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);
+ /*
+ * 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);
- /*
- * 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")));
+ oldcontext = MemoryContextSwitchTo(fcontext);
- 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))));
- }
+ /*
+ * 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 (fcache->readonly_func && !CommandIsReadOnly(stmt))
+ /*
+ * 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.
+ *
+ * Returns true if successful, false if there are no more queries.
+ */
+static bool
+init_execution_state(SQLFunctionCachePtr fcache)
+{
+ CachedPlanSource *plansource;
+ execution_state *preves = NULL;
+ execution_state *lasttages = NULL;
+ ListCell *lc;
+
+ /*
+ * Clean up after previous query, if there was one. Note that we just
+ * leak the old execution_state records until end of function execution;
+ * there aren't likely to be enough of them to matter.
+ */
+ if (fcache->cplan)
+ {
+ ReleaseCachedPlan(fcache->cplan, fcache->cowner);
+ fcache->cplan = NULL;
+ }
+ fcache->eslist = NULL;
+
+ /*
+ * 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++;
+
+ plansource = (CachedPlanSource *) list_nth(fcache->func->plansource_list,
+ fcache->next_query_index);
+ fcache->next_query_index++;
+
+ /*
+ * 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);
+
+ /*
+ * 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;
+
+ /*
+ * 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))));
-
- /* OK, build the execution_state for this query */
- newes = (execution_state *) palloc(sizeof(execution_state));
- if (preves)
- preves->next = newes;
- else
- firstes = 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;
-
- if (queryTree->canSetTag)
- lasttages = newes;
-
- preves = newes;
+ errmsg("%s is not allowed in an SQL function",
+ CreateCommandName(stmt->utilityStmt))));
}
- eslist = lappend(eslist, firstes);
+ 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;
+
+ if (stmt->canSetTag)
+ lasttages = newes;
+
+ preves = newes;
+ }
+
+ /*
+ * 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,176 +841,80 @@ 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;
- MemoryContext oldcontext;
- Oid rettype;
- TupleDesc rettupdesc;
- HeapTuple procedureTuple;
- Form_pg_proc procedureStruct;
- SQLFunctionCachePtr fcache;
+ int qindex;
+ bool islast;
+ CachedPlanSource *plansource;
List *queryTree_list;
- List *resulttlist;
- ListCell *lc;
- Datum tmp;
- bool isNull;
+ 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);
/*
- * Create memory context that holds all the SQLFunctionCache data. It
- * must be a child of whatever context holds the FmgrInfo.
+ * Parse and/or rewrite the query, creating a CachedPlanSource that holds
+ * a copy of the original parsetree.
*/
- fcontext = AllocSetContextCreate(finfo->fn_mcxt,
- "SQL function",
- ALLOCSET_DEFAULT_SIZES);
-
- oldcontext = MemoryContextSwitchTo(fcontext);
-
- /*
- * 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.
- */
- fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache));
- fcache->fcontext = fcontext;
- finfo->fn_extra = fcache;
-
- /*
- * get the procedure tuple corresponding to the given function Oid
- */
- procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(foid));
- if (!HeapTupleIsValid(procedureTuple))
- elog(ERROR, "cache lookup failed for function %u", foid);
- procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
-
- /*
- * 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);
-
- /*
- * Resolve any polymorphism, obtaining the actual result type, and the
- * corresponding tupdesc if it's a rowtype.
- */
- (void) get_call_result_type(fcinfo, &rettype, &rettupdesc);
-
- fcache->rettype = rettype;
-
- /* Fetch the typlen and byval info for the result type */
- get_typlenbyval(rettype, &fcache->typlen, &fcache->typbyval);
-
- /* Remember whether we're returning setof something */
- fcache->returnsSet = procedureStruct->proretset;
-
- /* Remember if function is STABLE/IMMUTABLE */
- fcache->readonly_func =
- (procedureStruct->provolatile != PROVOLATILE_VOLATILE);
-
- /*
- * 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);
-
- /*
- * And of course we need the function body text.
- */
- tmp = SysCacheGetAttrNotNull(PROCOID, procedureTuple, Anum_pg_proc_prosrc);
- fcache->src = 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)
+ if (!func->raw_source)
{
- Node *n;
- List *stored_query_list;
+ /* Source queries are already parse-analyzed */
+ Query *parsetree = list_nth_node(Query, func->source_list, qindex);
- n = stringToNode(TextDatumGetCString(tmp));
- if (IsA(n, List))
- stored_query_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);
- }
+ plansource = CreateCachedPlanForQuery(parsetree,
+ func->src,
+ CreateCommandTag((Node *) parsetree));
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_list = pg_rewrite_query(parsetree);
}
else
{
- List *raw_parsetree_list;
+ /* Source queries are raw parsetrees */
+ RawStmt *parsetree = list_nth_node(RawStmt, func->source_list, qindex);
- 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);
- }
+ 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_statements(queryTree_list);
+ check_sql_fn_statement(queryTree_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.)
+ * 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 fcache->returnsTuple according to whether we are returning
+ * 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
@@ -744,69 +922,283 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* 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);
+ if (islast)
+ func->returnsTuple = check_sql_stmt_retval(queryTree_list,
+ func->rettype,
+ func->rettupdesc,
+ func->prokind,
+ false);
/*
- * 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.)
+ * Now that check_sql_stmt_retval has done its thing, we can complete plan
+ * cache entry creation.
*/
- if (rettype != VOIDOID)
- {
- TupleTableSlot *slot = MakeSingleTupleTableSlot(NULL,
- &TTSOpsMinimalTuple);
+ CompleteCachedPlan(plansource,
+ queryTree_list,
+ NULL,
+ NULL,
+ 0,
+ (ParserSetupHook) sql_fn_parser_setup,
+ func->pinfo,
+ CURSOR_OPT_PARALLEL_OK | CURSOR_OPT_NO_SCROLL,
+ false);
- /*
- * 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);
- }
-
- 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))
- {
- /*
- * 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;
- }
-
- /* Finally, plan the queries */
- fcache->func_state = init_execution_state(queryTree_list,
- fcache,
- lazyEvalOK);
-
- /* Mark fcache with time of creation to show it's valid */
- fcache->lxid = MyProc->vxid.lxid;
- fcache->subxid = GetCurrentSubTransactionId();
-
- ReleaseSysCache(procedureTuple);
+ /*
+ * 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;
+ Datum tmp;
+ bool isNull;
+ List *source_list;
+
+ /*
+ * Setup error traceback support for ereport() during compile. (This is
+ * mainly useful for reporting parse errors from pg_parse_query.)
+ */
+ comperrcontext.callback = sql_compile_error_callback;
+ comperrcontext.arg = func;
+ comperrcontext.previous = error_context_stack;
+ error_context_stack = &comperrcontext;
+
+ /*
+ * 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.
+ */
+ hcontext = AllocSetContextCreate(CurrentMemoryContext,
+ "SQL function",
+ ALLOCSET_SMALL_SIZES);
+
+ /*
+ * Create the not-as-long-lived pcontext. We make this a child of
+ * hcontext so that it doesn't require separate deletion.
+ */
+ 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
+ */
+ func->fname = MemoryContextStrdup(hcontext,
+ NameStr(procedureStruct->proname));
+ MemoryContextSetIdentifier(hcontext, func->fname);
+
+ /*
+ * Resolve any polymorphism, obtaining the actual result type, and the
+ * corresponding tupdesc if it's a rowtype.
+ */
+ (void) get_call_result_type(fcinfo, &rettype, &rettupdesc);
+
+ 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, &func->typlen, &func->typbyval);
+
+ /* Remember whether we're returning setof something */
+ func->returnsSet = procedureStruct->proretset;
+
+ /* Remember if function is STABLE/IMMUTABLE */
+ 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.
+ */
+ 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);
+ 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);
+ if (!isNull)
+ {
+ /* Source queries are already parse-analyzed */
+ Node *n;
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ source_list = linitial_node(List, castNode(List, n));
+ else
+ source_list = list_make1(n);
+ func->raw_source = false;
+ }
+ else
+ {
+ /* Source queries are raw parsetrees */
+ source_list = pg_parse_query(func->src);
+ func->raw_source = true;
+ }
+
+ /*
+ * Note: we must save the number of queries so that we'll still remember
+ * how many there are after we discard source_list.
+ */
+ func->num_queries = list_length(source_list);
+
+ /* Save the source trees in pcontext for now. */
+ MemoryContextSwitchTo(pcontext);
+ func->source_list = copyObject(source_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * 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.
+ */
+ MemoryContextSetParent(hcontext, CacheMemoryContext);
+ func->hcontext = hcontext;
+
+ error_context_stack = comperrcontext.previous;
+}
+
+/*
+ * 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)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ DropCachedPlan(plansource);
+ }
+ func->plansource_list = NIL;
+
+ /*
+ * 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;
+}
+
+/*
+ * 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);
+
+ /*
+ * 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,81 +1454,64 @@ 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;
-
- 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;
- }
- }
-
- if (fcache == NULL)
- {
- init_sql_fcache(fcinfo, PG_GET_COLLATION(), lazyEvalOK);
- 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;
/*
- * Switch to context in which the fcache lives. This ensures that our
- * tuplestore etc will have sufficient lifetime. 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.)
+ * Now we can set up error traceback support for ereport()
*/
- oldcontext = MemoryContextSwitchTo(fcache->fcontext);
+ sqlerrcontext.callback = sql_exec_error_callback;
+ sqlerrcontext.arg = fcache;
+ sqlerrcontext.previous = error_context_stack;
+ error_context_stack = &sqlerrcontext;
/*
- * Find first unfinished query in function, and note whether it's the
- * first query.
+ * Build tuplestore to hold results, if we don't have one already. Make
+ * sure it's in a suitable context.
*/
- eslist = fcache->func_state;
- es = NULL;
- is_first = true;
- foreach(eslc, eslist)
- {
- es = (execution_state *) lfirst(eslc);
+ oldcontext = MemoryContextSwitchTo(tscontext);
- 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);
+ /*
+ * Switch to context in which the fcache lives. The sub-executor is
+ * responsible for deleting per-tuple information. (XXX in the case of a
+ * long-lived FmgrInfo, this policy potentially causes memory leakage, but
+ * it's not very clear where we could keep stuff instead. Fortunately,
+ * there are few if any cases where set-returning functions are invoked
+ * via FmgrInfos that would outlive the calling query.)
+ */
+ MemoryContextSwitchTo(fcache->fcontext);
+
+ /*
+ * Find first unfinished execution_state. If none, advance to the next
+ * query in function.
+ */
+ do
+ {
+ es = fcache->eslist;
+ while (es && es->status == F_EXEC_DONE)
+ es = es->next;
+ if (es)
+ break;
+ } while (init_execution_state(fcache));
+
/*
* Execute each command in the function one after another until we either
* run out of commands or get a result row from a lazily-evaluated SELECT.
@@ -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);
+
+ /* Release CachedPlan if we have one */
+ if (fcache->cplan)
+ ReleaseCachedPlan(fcache->cplan, fcache->cowner);
+
+ /* Release the cache */
+ MemoryContextDelete(fcache->fcontext);
}
-
- /* Release tuplestore if we have one */
- if (fcache->tstore)
- tuplestore_end(fcache->tstore);
- fcache->tstore = NULL;
-
/* 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)
+ check_sql_fn_statement(sublist);
+ }
+}
+
+/*
+ * As above, for a single sublist of Queries.
+ */
+static void
+check_sql_fn_statement(List *queryTreeList)
+{
+ ListCell *lc;
+
+ 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))
{
- Query *query = lfirst_node(Query, lc2);
+ CallStmt *stmt = (CallStmt *) query->utilityStmt;
- /*
- * 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")));
- }
+ 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
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 4622eaf709f..536ef187f9b 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4742,7 +4742,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
if (check_sql_fn_retval(list_make1(querytree_list),
result_type, rettupdesc,
funcform->prokind,
- false, NULL))
+ false))
goto fail; /* reject whole-tuple-result cases */
/*
@@ -5288,7 +5288,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (!check_sql_fn_retval(list_make1(querytree_list),
fexpr->funcresulttype, rettupdesc,
funcform->prokind,
- true, NULL) &&
+ true) &&
(functypclass == TYPEFUNC_COMPOSITE ||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN ||
functypclass == TYPEFUNC_RECORD))
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 76f58b3aca3..1f4d6adda52 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -591,6 +591,45 @@ analyze_requires_snapshot(RawStmt *parseTree)
return stmt_requires_parse_analysis(parseTree);
}
+/*
+ * query_requires_rewrite_plan()
+ * Returns true if rewriting or planning is non-trivial for this Query.
+ *
+ * This is much like stmt_requires_parse_analysis(), but applies one step
+ * further down the pipeline.
+ *
+ * We do not provide an equivalent of analyze_requires_snapshot(): callers
+ * can assume that any rewriting or planning activity needs a snapshot.
+ */
+bool
+query_requires_rewrite_plan(Query *query)
+{
+ bool result;
+
+ if (query->commandType != CMD_UTILITY)
+ {
+ /* All optimizable statements require rewriting/planning */
+ result = true;
+ }
+ else
+ {
+ /* This list should match stmt_requires_parse_analysis() */
+ switch (nodeTag(query->utilityStmt))
+ {
+ case T_DeclareCursorStmt:
+ case T_ExplainStmt:
+ case T_CreateTableAsStmt:
+ case T_CallStmt:
+ result = true;
+ break;
+ default:
+ result = false;
+ break;
+ }
+ }
+ return result;
+}
+
/*
* transformDeleteStmt -
* transforms a Delete Statement
diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile
index 5105018cb79..77b3e1a037b 100644
--- a/src/backend/utils/cache/Makefile
+++ b/src/backend/utils/cache/Makefile
@@ -16,6 +16,7 @@ OBJS = \
attoptcache.o \
catcache.o \
evtcache.o \
+ funccache.o \
inval.o \
lsyscache.o \
partcache.o \
diff --git a/src/backend/utils/cache/funccache.c b/src/backend/utils/cache/funccache.c
new file mode 100644
index 00000000000..150c502a612
--- /dev/null
+++ b/src/backend/utils/cache/funccache.c
@@ -0,0 +1,612 @@
+/*-------------------------------------------------------------------------
+ *
+ * funccache.c
+ * Function cache management.
+ *
+ * funccache.c manages a cache of function execution data. The cache
+ * is used by SQL-language and PL/pgSQL functions, and could be used by
+ * other function languages. Each cache entry is specific to the execution
+ * of a particular function (identified by OID) with specific input data
+ * types; so a polymorphic function could have many associated cache entries.
+ * Trigger functions similarly have a cache entry per trigger. These rules
+ * allow the cached data to be specific to the particular data types the
+ * function call will be dealing with.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/cache/funccache.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "commands/trigger.h"
+#include "common/hashfn.h"
+#include "funcapi.h"
+#include "utils/funccache.h"
+#include "utils/hsearch.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Hash table for cached functions
+ */
+static HTAB *cfunc_hashtable = NULL;
+
+typedef struct CachedFunctionHashEntry
+{
+ CachedFunctionHashKey key; /* hash key, must be first */
+ CachedFunction *function; /* points to data of language-specific size */
+} CachedFunctionHashEntry;
+
+#define FUNCS_PER_USER 128 /* initial table size */
+
+static uint32 cfunc_hash(const void *key, Size keysize);
+static int cfunc_match(const void *key1, const void *key2, Size keysize);
+
+
+/*
+ * Initialize the hash table on first use.
+ *
+ * The hash table will be in TopMemoryContext regardless of caller's context.
+ */
+static void
+cfunc_hashtable_init(void)
+{
+ HASHCTL ctl;
+
+ /* don't allow double-initialization */
+ Assert(cfunc_hashtable == NULL);
+
+ ctl.keysize = sizeof(CachedFunctionHashKey);
+ ctl.entrysize = sizeof(CachedFunctionHashEntry);
+ ctl.hash = cfunc_hash;
+ ctl.match = cfunc_match;
+ cfunc_hashtable = hash_create("Cached function hash",
+ FUNCS_PER_USER,
+ &ctl,
+ HASH_ELEM | HASH_FUNCTION | HASH_COMPARE);
+}
+
+/*
+ * cfunc_hash: hash function for cfunc hash table
+ *
+ * We need special hash and match functions to deal with the optional
+ * presence of a TupleDesc in the hash keys. As long as we have to do
+ * that, we might as well also be smart about not comparing unused
+ * elements of the argtypes arrays.
+ */
+static uint32
+cfunc_hash(const void *key, Size keysize)
+{
+ const CachedFunctionHashKey *k = (const CachedFunctionHashKey *) key;
+ uint32 h;
+
+ Assert(keysize == sizeof(CachedFunctionHashKey));
+ /* Hash all the fixed fields except callResultType */
+ h = DatumGetUInt32(hash_any((const unsigned char *) k,
+ offsetof(CachedFunctionHashKey, callResultType)));
+ /* Incorporate input argument types */
+ if (k->nargs > 0)
+ h = hash_combine(h,
+ DatumGetUInt32(hash_any((const unsigned char *) k->argtypes,
+ k->nargs * sizeof(Oid))));
+ /* Incorporate callResultType if present */
+ if (k->callResultType)
+ h = hash_combine(h, hashRowType(k->callResultType));
+ return h;
+}
+
+/*
+ * cfunc_match: match function to use with cfunc_hash
+ */
+static int
+cfunc_match(const void *key1, const void *key2, Size keysize)
+{
+ const CachedFunctionHashKey *k1 = (const CachedFunctionHashKey *) key1;
+ const CachedFunctionHashKey *k2 = (const CachedFunctionHashKey *) key2;
+
+ Assert(keysize == sizeof(CachedFunctionHashKey));
+ /* Compare all the fixed fields except callResultType */
+ if (memcmp(k1, k2, offsetof(CachedFunctionHashKey, callResultType)) != 0)
+ return 1; /* not equal */
+ /* Compare input argument types (we just verified that nargs matches) */
+ if (k1->nargs > 0 &&
+ memcmp(k1->argtypes, k2->argtypes, k1->nargs * sizeof(Oid)) != 0)
+ return 1; /* not equal */
+ /* Compare callResultType */
+ if (k1->callResultType)
+ {
+ if (k2->callResultType)
+ {
+ if (!equalRowTypes(k1->callResultType, k2->callResultType))
+ return 1; /* not equal */
+ }
+ else
+ return 1; /* not equal */
+ }
+ else
+ {
+ if (k2->callResultType)
+ return 1; /* not equal */
+ }
+ return 0; /* equal */
+}
+
+/*
+ * Look up the CachedFunction for the given hash key.
+ * Returns NULL if not present.
+ */
+static CachedFunction *
+cfunc_hashtable_lookup(CachedFunctionHashKey *func_key)
+{
+ CachedFunctionHashEntry *hentry;
+
+ if (cfunc_hashtable == NULL)
+ return NULL;
+
+ hentry = (CachedFunctionHashEntry *) hash_search(cfunc_hashtable,
+ func_key,
+ HASH_FIND,
+ NULL);
+ if (hentry)
+ return hentry->function;
+ else
+ return NULL;
+}
+
+/*
+ * Insert a hash table entry.
+ */
+static void
+cfunc_hashtable_insert(CachedFunction *function,
+ CachedFunctionHashKey *func_key)
+{
+ CachedFunctionHashEntry *hentry;
+ bool found;
+
+ if (cfunc_hashtable == NULL)
+ cfunc_hashtable_init();
+
+ hentry = (CachedFunctionHashEntry *) hash_search(cfunc_hashtable,
+ func_key,
+ HASH_ENTER,
+ &found);
+ if (found)
+ elog(WARNING, "trying to insert a function that already exists");
+
+ /*
+ * If there's a callResultType, copy it into TopMemoryContext. If we're
+ * unlucky enough for that to fail, leave the entry with null
+ * callResultType, which will probably never match anything.
+ */
+ if (func_key->callResultType)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ hentry->key.callResultType = NULL;
+ hentry->key.callResultType = CreateTupleDescCopy(func_key->callResultType);
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ hentry->function = function;
+
+ /* Set back-link from function to hashtable key */
+ function->fn_hashkey = &hentry->key;
+}
+
+/*
+ * Delete a hash table entry.
+ */
+static void
+cfunc_hashtable_delete(CachedFunction *function)
+{
+ CachedFunctionHashEntry *hentry;
+ TupleDesc tupdesc;
+
+ /* do nothing if not in table */
+ if (function->fn_hashkey == NULL)
+ return;
+
+ /*
+ * We need to free the callResultType if present, which is slightly tricky
+ * because it has to be valid during the hashtable search. Fortunately,
+ * because we have the hashkey back-link, we can grab that pointer before
+ * deleting the hashtable entry.
+ */
+ tupdesc = function->fn_hashkey->callResultType;
+
+ hentry = (CachedFunctionHashEntry *) hash_search(cfunc_hashtable,
+ function->fn_hashkey,
+ HASH_REMOVE,
+ NULL);
+ if (hentry == NULL)
+ elog(WARNING, "trying to delete function that does not exist");
+
+ /* Remove back link, which no longer points to allocated storage */
+ function->fn_hashkey = NULL;
+
+ /* Release the callResultType if present */
+ if (tupdesc)
+ FreeTupleDesc(tupdesc);
+}
+
+/*
+ * Compute the hashkey for a given function invocation
+ *
+ * The hashkey is returned into the caller-provided storage at *hashkey.
+ * Note however that if a callResultType is incorporated, we've not done
+ * anything about copying that.
+ */
+static void
+compute_function_hashkey(FunctionCallInfo fcinfo,
+ Form_pg_proc procStruct,
+ CachedFunctionHashKey *hashkey,
+ Size cacheEntrySize,
+ bool includeResultType,
+ bool forValidator)
+{
+ /* Make sure pad bytes within fixed part of the struct are zero */
+ memset(hashkey, 0, offsetof(CachedFunctionHashKey, argtypes));
+
+ /* get function OID */
+ hashkey->funcOid = fcinfo->flinfo->fn_oid;
+
+ /* get call context */
+ hashkey->isTrigger = CALLED_AS_TRIGGER(fcinfo);
+ hashkey->isEventTrigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
+
+ /* record cacheEntrySize so multiple languages can share hash table */
+ hashkey->cacheEntrySize = cacheEntrySize;
+
+ /*
+ * If DML trigger, include trigger's OID in the hash, so that each trigger
+ * usage gets a different hash entry, allowing for e.g. different relation
+ * rowtypes or transition table names. In validation mode we do not know
+ * what relation or transition table names are intended to be used, so we
+ * leave trigOid zero; the hash entry built in this case will never be
+ * used for any actual calls.
+ *
+ * We don't currently need to distinguish different event trigger usages
+ * in the same way, since the special parameter variables don't vary in
+ * type in that case.
+ */
+ if (hashkey->isTrigger && !forValidator)
+ {
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+
+ hashkey->trigOid = trigdata->tg_trigger->tgoid;
+ }
+
+ /* get input collation, if known */
+ hashkey->inputCollation = fcinfo->fncollation;
+
+ /*
+ * We include only input arguments in the hash key, since output argument
+ * types can be deduced from those, and it would require extra cycles to
+ * include the output arguments. But we have to resolve any polymorphic
+ * argument types to the real types for the call.
+ */
+ if (procStruct->pronargs > 0)
+ {
+ hashkey->nargs = procStruct->pronargs;
+ memcpy(hashkey->argtypes, procStruct->proargtypes.values,
+ procStruct->pronargs * sizeof(Oid));
+ cfunc_resolve_polymorphic_argtypes(procStruct->pronargs,
+ hashkey->argtypes,
+ NULL, /* all args are inputs */
+ fcinfo->flinfo->fn_expr,
+ forValidator,
+ NameStr(procStruct->proname));
+ }
+
+ /*
+ * While regular OUT arguments are sufficiently represented by the
+ * resolved input arguments, a function returning composite has additional
+ * variability: ALTER TABLE/ALTER TYPE could affect what it returns. Also,
+ * a function returning RECORD may depend on a column definition list to
+ * determine its output rowtype. If the caller needs the exact result
+ * type to be part of the hash lookup key, we must run
+ * get_call_result_type() to find that out.
+ */
+ if (includeResultType)
+ {
+ Oid resultTypeId;
+ TupleDesc tupdesc;
+
+ switch (get_call_result_type(fcinfo, &resultTypeId, &tupdesc))
+ {
+ case TYPEFUNC_COMPOSITE:
+ case TYPEFUNC_COMPOSITE_DOMAIN:
+ hashkey->callResultType = tupdesc;
+ break;
+ default:
+ /* scalar result, or indeterminate rowtype */
+ break;
+ }
+ }
+}
+
+/*
+ * This is the same as the standard resolve_polymorphic_argtypes() function,
+ * except that:
+ * 1. We go ahead and report the error if we can't resolve the types.
+ * 2. We treat RECORD-type input arguments (not output arguments) as if
+ * they were polymorphic, replacing their types with the actual input
+ * types if we can determine those. This allows us to create a separate
+ * function cache entry for each named composite type passed to such an
+ * argument.
+ * 3. In validation mode, we have no inputs to look at, so assume that
+ * polymorphic arguments are integer, integer-array or integer-range.
+ */
+void
+cfunc_resolve_polymorphic_argtypes(int numargs,
+ Oid *argtypes, char *argmodes,
+ Node *call_expr, bool forValidator,
+ const char *proname)
+{
+ int i;
+
+ if (!forValidator)
+ {
+ int inargno;
+
+ /* normal case, pass to standard routine */
+ if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
+ call_expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("could not determine actual argument "
+ "type for polymorphic function \"%s\"",
+ proname)));
+ /* also, treat RECORD inputs (but not outputs) as polymorphic */
+ inargno = 0;
+ for (i = 0; i < numargs; i++)
+ {
+ char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ continue;
+ if (argtypes[i] == RECORDOID || argtypes[i] == RECORDARRAYOID)
+ {
+ Oid resolvedtype = get_call_expr_argtype(call_expr,
+ inargno);
+
+ if (OidIsValid(resolvedtype))
+ argtypes[i] = resolvedtype;
+ }
+ inargno++;
+ }
+ }
+ else
+ {
+ /* special validation case (no need to do anything for RECORD) */
+ for (i = 0; i < numargs; i++)
+ {
+ switch (argtypes[i])
+ {
+ case ANYELEMENTOID:
+ case ANYNONARRAYOID:
+ case ANYENUMOID: /* XXX dubious */
+ case ANYCOMPATIBLEOID:
+ case ANYCOMPATIBLENONARRAYOID:
+ argtypes[i] = INT4OID;
+ break;
+ case ANYARRAYOID:
+ case ANYCOMPATIBLEARRAYOID:
+ argtypes[i] = INT4ARRAYOID;
+ break;
+ case ANYRANGEOID:
+ case ANYCOMPATIBLERANGEOID:
+ argtypes[i] = INT4RANGEOID;
+ break;
+ case ANYMULTIRANGEOID:
+ argtypes[i] = INT4MULTIRANGEOID;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * delete_function - clean up as much as possible of a stale function cache
+ *
+ * We can't release the CachedFunction struct itself, because of the
+ * possibility that there are fn_extra pointers to it. We can release
+ * the subsidiary storage, but only if there are no active evaluations
+ * in progress. Otherwise we'll just leak that storage. Since the
+ * case would only occur if a pg_proc update is detected during a nested
+ * recursive call on the function, a leak seems acceptable.
+ *
+ * Note that this can be called more than once if there are multiple fn_extra
+ * pointers to the same function cache. Hence be careful not to do things
+ * twice.
+ */
+static void
+delete_function(CachedFunction *func)
+{
+ /* remove function from hash table (might be done already) */
+ cfunc_hashtable_delete(func);
+
+ /* release the function's storage if safe and not done already */
+ if (func->use_count == 0 &&
+ func->dcallback != NULL)
+ {
+ func->dcallback(func);
+ func->dcallback = NULL;
+ }
+}
+
+/*
+ * Compile a cached function, if no existing cache entry is suitable.
+ *
+ * fcinfo is the current call information.
+ *
+ * function should be NULL or the result of a previous call of
+ * cached_function_compile() for the same fcinfo. The caller will
+ * typically save the result in fcinfo->flinfo->fn_extra, or in a
+ * field of a struct pointed to by fn_extra, to re-use in later
+ * calls within the same query.
+ *
+ * ccallback and dcallback are function-language-specific callbacks to
+ * compile and delete a cached function entry. dcallback can be NULL
+ * if there's nothing for it to do.
+ *
+ * cacheEntrySize is the function-language-specific size of the cache entry
+ * (which embeds a CachedFunction struct and typically has many more fields
+ * after that).
+ *
+ * If includeResultType is true and the function returns composite,
+ * include the actual result descriptor in the cache lookup key.
+ *
+ * If forValidator is true, we're only compiling for validation purposes,
+ * and so some checks are skipped.
+ *
+ * Note: it's important for this to fall through quickly if the function
+ * has already been compiled.
+ *
+ * Note: this function leaves the "use_count" field as zero. The caller
+ * is expected to increment the use_count and decrement it when done with
+ * the cache entry.
+ */
+CachedFunction *
+cached_function_compile(FunctionCallInfo fcinfo,
+ CachedFunction *function,
+ CachedFunctionCompileCallback ccallback,
+ CachedFunctionDeleteCallback dcallback,
+ Size cacheEntrySize,
+ bool includeResultType,
+ bool forValidator)
+{
+ Oid funcOid = fcinfo->flinfo->fn_oid;
+ HeapTuple procTup;
+ Form_pg_proc procStruct;
+ CachedFunctionHashKey hashkey;
+ bool function_valid = false;
+ bool hashkey_valid = false;
+
+ /*
+ * Lookup the pg_proc tuple by Oid; we'll need it in any case
+ */
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function %u", funcOid);
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /*
+ * Do we already have a cache entry for the current FmgrInfo? If not, try
+ * to find one in the hash table.
+ */
+recheck:
+ if (!function)
+ {
+ /* Compute hashkey using function signature and actual arg types */
+ compute_function_hashkey(fcinfo, procStruct, &hashkey,
+ cacheEntrySize, includeResultType,
+ forValidator);
+ hashkey_valid = true;
+
+ /* And do the lookup */
+ function = cfunc_hashtable_lookup(&hashkey);
+ }
+
+ if (function)
+ {
+ /* We have a compiled function, but is it still valid? */
+ if (function->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ ItemPointerEquals(&function->fn_tid, &procTup->t_self))
+ function_valid = true;
+ else
+ {
+ /*
+ * Nope, so remove it from hashtable and try to drop associated
+ * storage (if not done already).
+ */
+ delete_function(function);
+
+ /*
+ * If the function isn't in active use then we can overwrite the
+ * func struct with new data, allowing any other existing fn_extra
+ * pointers to make use of the new definition on their next use.
+ * If it is in use then just leave it alone and make a new one.
+ * (The active invocations will run to completion using the
+ * previous definition, and then the cache entry will just be
+ * leaked; doesn't seem worth adding code to clean it up, given
+ * what a corner case this is.)
+ *
+ * If we found the function struct via fn_extra then it's possible
+ * a replacement has already been made, so go back and recheck the
+ * hashtable.
+ */
+ if (function->use_count != 0)
+ {
+ function = NULL;
+ if (!hashkey_valid)
+ goto recheck;
+ }
+ }
+ }
+
+ /*
+ * If the function wasn't found or was out-of-date, we have to compile it.
+ */
+ if (!function_valid)
+ {
+ /*
+ * Calculate hashkey if we didn't already; we'll need it to store the
+ * completed function.
+ */
+ if (!hashkey_valid)
+ compute_function_hashkey(fcinfo, procStruct, &hashkey,
+ cacheEntrySize, includeResultType,
+ forValidator);
+
+ /*
+ * Create the new function struct, if not done already. The function
+ * structs are never thrown away, so keep them in TopMemoryContext.
+ */
+ Assert(cacheEntrySize >= sizeof(CachedFunction));
+ if (function == NULL)
+ {
+ function = (CachedFunction *)
+ MemoryContextAllocZero(TopMemoryContext, cacheEntrySize);
+ }
+ else
+ {
+ /* re-using a previously existing struct, so clear it out */
+ memset(function, 0, cacheEntrySize);
+ }
+
+ /*
+ * Fill in the CachedFunction part. fn_hashkey and use_count remain
+ * zeroes for now.
+ */
+ function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ function->fn_tid = procTup->t_self;
+ function->dcallback = dcallback;
+
+ /*
+ * Do the hard, language-specific part.
+ */
+ ccallback(fcinfo, procTup, &hashkey, function, forValidator);
+
+ /*
+ * Add the completed struct to the hash table.
+ */
+ cfunc_hashtable_insert(function, &hashkey);
+ }
+
+ ReleaseSysCache(procTup);
+
+ /*
+ * Finally return the compiled function
+ */
+ return function;
+}
diff --git a/src/backend/utils/cache/meson.build b/src/backend/utils/cache/meson.build
index 104b28737d7..a1784dce585 100644
--- a/src/backend/utils/cache/meson.build
+++ b/src/backend/utils/cache/meson.build
@@ -4,6 +4,7 @@ backend_sources += files(
'attoptcache.c',
'catcache.c',
'evtcache.c',
+ 'funccache.c',
'inval.c',
'lsyscache.c',
'partcache.c',
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 6c2979d5c82..3b681647060 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -14,7 +14,7 @@
* Cache invalidation is driven off sinval events. Any CachedPlanSource
* that matches the event is marked invalid, as is its generic CachedPlan
* if it has one. When (and if) the next demand for a cached plan occurs,
- * parse analysis and rewrite is repeated to build a new valid query tree,
+ * parse analysis and/or rewrite is repeated to build a new valid query tree,
* and then planning is performed as normal. We also force re-analysis and
* re-planning if the active search_path is different from the previous time
* or, if RLS is involved, if the user changes or the RLS environment changes.
@@ -63,6 +63,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/lmgr.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
@@ -74,18 +75,6 @@
#include "utils/syscache.h"
-/*
- * We must skip "overhead" operations that involve database access when the
- * cached plan's subject statement is a transaction control command or one
- * that requires a snapshot not to be set yet (such as SET or LOCK). More
- * generally, statements that do not require parse analysis/rewrite/plan
- * activity never need to be revalidated, so we can treat them all like that.
- * For the convenience of postgres.c, treat empty statements that way too.
- */
-#define StmtPlanRequiresRevalidation(plansource) \
- ((plansource)->raw_parse_tree != NULL && \
- stmt_requires_parse_analysis((plansource)->raw_parse_tree))
-
/*
* This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
* those that are in long-lived storage and are examined for sinval events).
@@ -100,6 +89,8 @@ static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list);
static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list);
static void ReleaseGenericPlan(CachedPlanSource *plansource);
+static bool StmtPlanRequiresRevalidation(CachedPlanSource *plansource);
+static bool BuildingPlanRequiresSnapshot(CachedPlanSource *plansource);
static List *RevalidateCachedQuery(CachedPlanSource *plansource,
QueryEnvironment *queryEnv,
bool release_generic);
@@ -166,7 +157,7 @@ InitPlanCache(void)
}
/*
- * CreateCachedPlan: initially create a plan cache entry.
+ * CreateCachedPlan: initially create a plan cache entry for a raw parse tree.
*
* Creation of a cached plan is divided into two steps, CreateCachedPlan and
* CompleteCachedPlan. CreateCachedPlan should be called after running the
@@ -220,6 +211,7 @@ CreateCachedPlan(RawStmt *raw_parse_tree,
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
plansource->magic = CACHEDPLANSOURCE_MAGIC;
plansource->raw_parse_tree = copyObject(raw_parse_tree);
+ plansource->analyzed_parse_tree = NULL;
plansource->query_string = pstrdup(query_string);
MemoryContextSetIdentifier(source_context, plansource->query_string);
plansource->commandTag = commandTag;
@@ -227,6 +219,8 @@ CreateCachedPlan(RawStmt *raw_parse_tree,
plansource->num_params = 0;
plansource->parserSetup = NULL;
plansource->parserSetupArg = NULL;
+ plansource->postRewrite = NULL;
+ plansource->postRewriteArg = NULL;
plansource->cursor_options = 0;
plansource->fixed_result = false;
plansource->resultDesc = NULL;
@@ -255,6 +249,34 @@ CreateCachedPlan(RawStmt *raw_parse_tree,
return plansource;
}
+/*
+ * CreateCachedPlanForQuery: initially create a plan cache entry for a Query.
+ *
+ * This is used in the same way as CreateCachedPlan, except that the source
+ * query has already been through parse analysis, and the plancache will never
+ * try to re-do that step.
+ *
+ * Currently this is used only for new-style SQL functions, where we have a
+ * Query from the function's prosqlbody, but no source text. The query_string
+ * is typically empty, but is required anyway.
+ */
+CachedPlanSource *
+CreateCachedPlanForQuery(Query *analyzed_parse_tree,
+ const char *query_string,
+ CommandTag commandTag)
+{
+ CachedPlanSource *plansource;
+ MemoryContext oldcxt;
+
+ /* Rather than duplicating CreateCachedPlan, just do this: */
+ plansource = CreateCachedPlan(NULL, query_string, commandTag);
+ oldcxt = MemoryContextSwitchTo(plansource->context);
+ plansource->analyzed_parse_tree = copyObject(analyzed_parse_tree);
+ MemoryContextSwitchTo(oldcxt);
+
+ return plansource;
+}
+
/*
* CreateOneShotCachedPlan: initially create a one-shot plan cache entry.
*
@@ -289,12 +311,15 @@ CreateOneShotCachedPlan(RawStmt *raw_parse_tree,
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
plansource->magic = CACHEDPLANSOURCE_MAGIC;
plansource->raw_parse_tree = raw_parse_tree;
+ plansource->analyzed_parse_tree = NULL;
plansource->query_string = query_string;
plansource->commandTag = commandTag;
plansource->param_types = NULL;
plansource->num_params = 0;
plansource->parserSetup = NULL;
plansource->parserSetupArg = NULL;
+ plansource->postRewrite = NULL;
+ plansource->postRewriteArg = NULL;
plansource->cursor_options = 0;
plansource->fixed_result = false;
plansource->resultDesc = NULL;
@@ -464,6 +489,29 @@ CompleteCachedPlan(CachedPlanSource *plansource,
plansource->is_valid = true;
}
+/*
+ * SetPostRewriteHook: set a hook to modify post-rewrite query trees
+ *
+ * Some callers have a need to modify the query trees between rewriting and
+ * planning. In the initial call to CompleteCachedPlan, it's assumed such
+ * work was already done on the querytree_list. However, if we're forced
+ * to replan, it will need to be done over. The caller can set this hook
+ * to provide code to make that happen.
+ *
+ * postRewriteArg is just passed verbatim to the hook. As with parserSetupArg,
+ * it is caller's responsibility that the referenced data remains
+ * valid for as long as the CachedPlanSource exists.
+ */
+void
+SetPostRewriteHook(CachedPlanSource *plansource,
+ PostRewriteHook postRewrite,
+ void *postRewriteArg)
+{
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ plansource->postRewrite = postRewrite;
+ plansource->postRewriteArg = postRewriteArg;
+}
+
/*
* SaveCachedPlan: save a cached plan permanently
*
@@ -566,6 +614,42 @@ ReleaseGenericPlan(CachedPlanSource *plansource)
}
}
+/*
+ * We must skip "overhead" operations that involve database access when the
+ * cached plan's subject statement is a transaction control command or one
+ * that requires a snapshot not to be set yet (such as SET or LOCK). More
+ * generally, statements that do not require parse analysis/rewrite/plan
+ * activity never need to be revalidated, so we can treat them all like that.
+ * For the convenience of postgres.c, treat empty statements that way too.
+ */
+static bool
+StmtPlanRequiresRevalidation(CachedPlanSource *plansource)
+{
+ if (plansource->raw_parse_tree != NULL)
+ return stmt_requires_parse_analysis(plansource->raw_parse_tree);
+ else if (plansource->analyzed_parse_tree != NULL)
+ return query_requires_rewrite_plan(plansource->analyzed_parse_tree);
+ /* empty query never needs revalidation */
+ return false;
+}
+
+/*
+ * Determine if creating a plan for this CachedPlanSource requires a snapshot.
+ * In fact this function matches StmtPlanRequiresRevalidation(), but we want
+ * to preserve the distinction between stmt_requires_parse_analysis() and
+ * analyze_requires_snapshot().
+ */
+static bool
+BuildingPlanRequiresSnapshot(CachedPlanSource *plansource)
+{
+ if (plansource->raw_parse_tree != NULL)
+ return analyze_requires_snapshot(plansource->raw_parse_tree);
+ else if (plansource->analyzed_parse_tree != NULL)
+ return query_requires_rewrite_plan(plansource->analyzed_parse_tree);
+ /* empty query never needs a snapshot */
+ return false;
+}
+
/*
* RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree.
*
@@ -592,7 +676,6 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
bool release_generic)
{
bool snapshot_set;
- RawStmt *rawtree;
List *tlist; /* transient query-tree list */
List *qlist; /* permanent query-tree list */
TupleDesc resultDesc;
@@ -615,7 +698,10 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
/*
* If the query is currently valid, we should have a saved search_path ---
* check to see if that matches the current environment. If not, we want
- * to force replan.
+ * to force replan. (We could almost ignore this consideration when
+ * working from an analyzed parse tree; but there are scenarios where
+ * planning can have search_path-dependent results, for example if it
+ * inlines an old-style SQL function.)
*/
if (plansource->is_valid)
{
@@ -662,9 +748,9 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
}
/*
- * Discard the no-longer-useful query tree. (Note: we don't want to do
- * this any earlier, else we'd not have been able to release locks
- * correctly in the race condition case.)
+ * Discard the no-longer-useful rewritten query tree. (Note: we don't
+ * want to do this any earlier, else we'd not have been able to release
+ * locks correctly in the race condition case.)
*/
plansource->is_valid = false;
plansource->query_list = NIL;
@@ -711,25 +797,52 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
}
/*
- * Run parse analysis and rule rewriting. The parser tends to scribble on
- * its input, so we must copy the raw parse tree to prevent corruption of
- * the cache.
+ * Run parse analysis (if needed) and rule rewriting.
*/
- rawtree = copyObject(plansource->raw_parse_tree);
- if (rawtree == NULL)
- tlist = NIL;
- else if (plansource->parserSetup != NULL)
- tlist = pg_analyze_and_rewrite_withcb(rawtree,
- plansource->query_string,
- plansource->parserSetup,
- plansource->parserSetupArg,
- queryEnv);
+ if (plansource->raw_parse_tree != NULL)
+ {
+ /* Source is raw parse tree */
+ RawStmt *rawtree;
+
+ /*
+ * The parser tends to scribble on its input, so we must copy the raw
+ * parse tree to prevent corruption of the cache.
+ */
+ rawtree = copyObject(plansource->raw_parse_tree);
+ if (plansource->parserSetup != NULL)
+ tlist = pg_analyze_and_rewrite_withcb(rawtree,
+ plansource->query_string,
+ plansource->parserSetup,
+ plansource->parserSetupArg,
+ queryEnv);
+ else
+ tlist = pg_analyze_and_rewrite_fixedparams(rawtree,
+ plansource->query_string,
+ plansource->param_types,
+ plansource->num_params,
+ queryEnv);
+ }
+ else if (plansource->analyzed_parse_tree != NULL)
+ {
+ /* Source is pre-analyzed query, so we only need to rewrite */
+ Query *analyzed_tree;
+
+ /* The rewriter scribbles on its input, too, so copy */
+ analyzed_tree = copyObject(plansource->analyzed_parse_tree);
+ /* Acquire locks needed before rewriting ... */
+ AcquireRewriteLocks(analyzed_tree, true, false);
+ /* ... and do it */
+ tlist = pg_rewrite_query(analyzed_tree);
+ }
else
- tlist = pg_analyze_and_rewrite_fixedparams(rawtree,
- plansource->query_string,
- plansource->param_types,
- plansource->num_params,
- queryEnv);
+ {
+ /* Empty query, nothing to do */
+ tlist = NIL;
+ }
+
+ /* Apply post-rewrite callback if there is one */
+ if (plansource->postRewrite != NULL)
+ plansource->postRewrite(tlist, plansource->postRewriteArg);
/* Release snapshot if we got one */
if (snapshot_set)
@@ -963,8 +1076,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
*/
snapshot_set = false;
if (!ActiveSnapshotSet() &&
- plansource->raw_parse_tree &&
- analyze_requires_snapshot(plansource->raw_parse_tree))
+ BuildingPlanRequiresSnapshot(plansource))
{
PushActiveSnapshot(GetTransactionSnapshot());
snapshot_set = true;
@@ -1703,6 +1815,7 @@ CopyCachedPlan(CachedPlanSource *plansource)
newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
newsource->magic = CACHEDPLANSOURCE_MAGIC;
newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
+ newsource->analyzed_parse_tree = copyObject(plansource->analyzed_parse_tree);
newsource->query_string = pstrdup(plansource->query_string);
MemoryContextSetIdentifier(source_context, newsource->query_string);
newsource->commandTag = plansource->commandTag;
@@ -1718,6 +1831,8 @@ CopyCachedPlan(CachedPlanSource *plansource)
newsource->num_params = plansource->num_params;
newsource->parserSetup = plansource->parserSetup;
newsource->parserSetupArg = plansource->parserSetupArg;
+ newsource->postRewrite = plansource->postRewrite;
+ newsource->postRewriteArg = plansource->postRewriteArg;
newsource->cursor_options = plansource->cursor_options;
newsource->fixed_result = plansource->fixed_result;
if (plansource->resultDesc)
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index a6ae2e72d79..58bdff9b039 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -48,8 +48,7 @@ extern void check_sql_fn_statements(List *queryTreeLists);
extern bool check_sql_fn_retval(List *queryTreeLists,
Oid rettype, TupleDesc rettupdesc,
char prokind,
- bool insertDroppedCols,
- List **resultTargetList);
+ bool insertDroppedCols);
extern DestReceiver *CreateSQLFunctionDestReceiver(void);
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index f1bd18c49f2..f29ed03b476 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -52,6 +52,7 @@ extern Query *transformStmt(ParseState *pstate, Node *parseTree);
extern bool stmt_requires_parse_analysis(RawStmt *parseTree);
extern bool analyze_requires_snapshot(RawStmt *parseTree);
+extern bool query_requires_rewrite_plan(Query *query);
extern const char *LCS_asString(LockClauseStrength strength);
extern void CheckSelectLocking(Query *qry, LockClauseStrength strength);
diff --git a/src/include/utils/funccache.h b/src/include/utils/funccache.h
new file mode 100644
index 00000000000..e0112ebfa11
--- /dev/null
+++ b/src/include/utils/funccache.h
@@ -0,0 +1,134 @@
+/*-------------------------------------------------------------------------
+ *
+ * funccache.h
+ * Function cache definitions.
+ *
+ * See funccache.c for comments.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/funccache.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FUNCCACHE_H
+#define FUNCCACHE_H
+
+#include "access/htup_details.h"
+#include "fmgr.h"
+#include "storage/itemptr.h"
+
+struct CachedFunctionHashKey; /* forward references */
+struct CachedFunction;
+
+/*
+ * Callback that cached_function_compile() invokes when it's necessary to
+ * compile a cached function. The callback must fill in *function (except
+ * for the fields of struct CachedFunction), or throw an error if trouble.
+ * fcinfo: current call information
+ * procTup: function's pg_proc row from catcache
+ * hashkey: hash key that will be used for the function
+ * function: pre-zeroed workspace, of size passed to cached_function_compile()
+ * forValidator: passed through from cached_function_compile()
+ */
+typedef void (*CachedFunctionCompileCallback) (FunctionCallInfo fcinfo,
+ HeapTuple procTup,
+ const struct CachedFunctionHashKey *hashkey,
+ struct CachedFunction *function,
+ bool forValidator);
+
+/*
+ * Callback called when discarding a cache entry. Free any free-able
+ * subsidiary data of cfunc, but not the struct CachedFunction itself.
+ */
+typedef void (*CachedFunctionDeleteCallback) (struct CachedFunction *cfunc);
+
+/*
+ * Hash lookup key for functions. This must account for all aspects
+ * of a specific call that might lead to different data types or
+ * collations being used within the function.
+ */
+typedef struct CachedFunctionHashKey
+{
+ Oid funcOid;
+
+ bool isTrigger; /* true if called as a DML trigger */
+ bool isEventTrigger; /* true if called as an event trigger */
+
+ /* be careful that pad bytes in this struct get zeroed! */
+
+ /*
+ * We include the language-specific size of the function's cache entry in
+ * the cache key. This covers the case where CREATE OR REPLACE FUNCTION
+ * is used to change the implementation language, and the new language
+ * also uses funccache.c but needs a different-sized cache entry.
+ */
+ Size cacheEntrySize;
+
+ /*
+ * For a trigger function, the OID of the trigger is part of the hash key
+ * --- we want to compile the trigger function separately for each trigger
+ * it is used with, in case the rowtype or transition table names are
+ * different. Zero if not called as a DML trigger.
+ */
+ Oid trigOid;
+
+ /*
+ * We must include the input collation as part of the hash key too,
+ * because we have to generate different plans (with different Param
+ * collations) for different collation settings.
+ */
+ Oid inputCollation;
+
+ /* Number of arguments (counting input arguments only, ie pronargs) */
+ int nargs;
+
+ /* If you change anything below here, fix hashing code in funccache.c! */
+
+ /*
+ * If relevant, the result descriptor for a function returning composite.
+ */
+ TupleDesc callResultType;
+
+ /*
+ * Input argument types, with any polymorphic types resolved to actual
+ * types. Only the first nargs entries are valid.
+ */
+ Oid argtypes[FUNC_MAX_ARGS];
+} CachedFunctionHashKey;
+
+/*
+ * Representation of a compiled function. This struct contains just the
+ * fields that funccache.c needs to deal with. It will typically be
+ * embedded in a larger struct containing function-language-specific data.
+ */
+typedef struct CachedFunction
+{
+ /* back-link to hashtable entry, or NULL if not in hash table */
+ CachedFunctionHashKey *fn_hashkey;
+ /* xmin and ctid of function's pg_proc row; used to detect invalidation */
+ TransactionId fn_xmin;
+ ItemPointerData fn_tid;
+ /* deletion callback */
+ CachedFunctionDeleteCallback dcallback;
+
+ /* this field changes when the function is used: */
+ uint64 use_count;
+} CachedFunction;
+
+extern CachedFunction *cached_function_compile(FunctionCallInfo fcinfo,
+ CachedFunction *function,
+ CachedFunctionCompileCallback ccallback,
+ CachedFunctionDeleteCallback dcallback,
+ Size cacheEntrySize,
+ bool includeResultType,
+ bool forValidator);
+extern void cfunc_resolve_polymorphic_argtypes(int numargs,
+ Oid *argtypes,
+ char *argmodes,
+ Node *call_expr,
+ bool forValidator,
+ const char *proname);
+
+#endif /* FUNCCACHE_H */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 199cc323a28..07ec5318db7 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -25,7 +25,8 @@
#include "utils/resowner.h"
-/* Forward declaration, to avoid including parsenodes.h here */
+/* Forward declarations, to avoid including parsenodes.h here */
+struct Query;
struct RawStmt;
/* possible values for plan_cache_mode */
@@ -39,18 +40,31 @@ typedef enum
/* GUC parameter */
extern PGDLLIMPORT int plan_cache_mode;
+/* Optional callback to editorialize on rewritten parse trees */
+typedef void (*PostRewriteHook) (List *querytree_list, void *arg);
+
#define CACHEDPLANSOURCE_MAGIC 195726186
#define CACHEDPLAN_MAGIC 953717834
#define CACHEDEXPR_MAGIC 838275847
/*
* CachedPlanSource (which might better have been called CachedQuery)
- * represents a SQL query that we expect to use multiple times. It stores
- * the query source text, the raw parse tree, and the analyzed-and-rewritten
+ * represents a SQL query that we expect to use multiple times. It stores the
+ * query source text, the source parse tree, and the analyzed-and-rewritten
* query tree, as well as adjunct data. Cache invalidation can happen as a
* result of DDL affecting objects used by the query. In that case we discard
* the analyzed-and-rewritten query tree, and rebuild it when next needed.
*
+ * There are two ways in which the source query can be represented: either
+ * as a raw parse tree, or as an analyzed-but-not-rewritten parse tree.
+ * In the latter case we expect that cache invalidation need not affect
+ * the parse-analysis results, only the rewriting and planning steps.
+ * Only one of raw_parse_tree and analyzed_parse_tree can be non-NULL.
+ * (If both are NULL, the CachedPlanSource represents an empty query.)
+ * Note that query_string is typically just an empty string when the
+ * source query is an analyzed parse tree; also, param_types, num_params,
+ * parserSetup, and parserSetupArg will not be used.
+ *
* An actual execution plan, represented by CachedPlan, is derived from the
* CachedPlanSource when we need to execute the query. The plan could be
* either generic (usable with any set of plan parameters) or custom (for a
@@ -78,7 +92,7 @@ extern PGDLLIMPORT int plan_cache_mode;
* though it may be useful if the CachedPlan can be discarded early.)
*
* A CachedPlanSource has two associated memory contexts: one that holds the
- * struct itself, the query source text and the raw parse tree, and another
+ * struct itself, the query source text and the source parse tree, and another
* context that holds the rewritten query tree and associated data. This
* allows the query tree to be discarded easily when it is invalidated.
*
@@ -94,12 +108,15 @@ typedef struct CachedPlanSource
{
int magic; /* should equal CACHEDPLANSOURCE_MAGIC */
struct RawStmt *raw_parse_tree; /* output of raw_parser(), or NULL */
+ struct Query *analyzed_parse_tree; /* analyzed parse tree, or NULL */
const char *query_string; /* source text of query */
CommandTag commandTag; /* command tag for query */
Oid *param_types; /* array of parameter type OIDs, or NULL */
int num_params; /* length of param_types array */
ParserSetupHook parserSetup; /* alternative parameter spec method */
void *parserSetupArg;
+ PostRewriteHook postRewrite; /* see SetPostRewriteHook */
+ void *postRewriteArg;
int cursor_options; /* cursor options used for planning */
bool fixed_result; /* disallow change in result tupdesc? */
TupleDesc resultDesc; /* result type; NULL = doesn't return tuples */
@@ -196,6 +213,9 @@ extern void ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner);
extern CachedPlanSource *CreateCachedPlan(struct RawStmt *raw_parse_tree,
const char *query_string,
CommandTag commandTag);
+extern CachedPlanSource *CreateCachedPlanForQuery(struct Query *analyzed_parse_tree,
+ const char *query_string,
+ CommandTag commandTag);
extern CachedPlanSource *CreateOneShotCachedPlan(struct RawStmt *raw_parse_tree,
const char *query_string,
CommandTag commandTag);
@@ -208,6 +228,9 @@ extern void CompleteCachedPlan(CachedPlanSource *plansource,
void *parserSetupArg,
int cursor_options,
bool fixed_result);
+extern void SetPostRewriteHook(CachedPlanSource *plansource,
+ PostRewriteHook postRewrite,
+ void *postRewriteArg);
extern void SaveCachedPlan(CachedPlanSource *plansource);
extern void DropCachedPlan(CachedPlanSource *plansource);
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 6fdba95962d..519f7695d7c 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -52,20 +52,6 @@ PLpgSQL_function *plpgsql_curr_compile;
/* A context appropriate for short-term allocs during compilation */
MemoryContext plpgsql_compile_tmp_cxt;
-/* ----------
- * Hash table for compiled functions
- * ----------
- */
-static HTAB *plpgsql_HashTable = NULL;
-
-typedef struct plpgsql_hashent
-{
- PLpgSQL_func_hashkey key;
- PLpgSQL_function *function;
-} plpgsql_HashEnt;
-
-#define FUNCS_PER_USER 128 /* initial table size */
-
/* ----------
* Lookup table for EXCEPTION condition names
* ----------
@@ -86,11 +72,11 @@ static const ExceptionLabelMap exception_label_map[] = {
* static prototypes
* ----------
*/
-static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
- HeapTuple procTup,
- PLpgSQL_function *function,
- PLpgSQL_func_hashkey *hashkey,
- bool forValidator);
+static void plpgsql_compile_callback(FunctionCallInfo fcinfo,
+ HeapTuple procTup,
+ const CachedFunctionHashKey *hashkey,
+ CachedFunction *cfunc,
+ bool forValidator);
static void plpgsql_compile_error_callback(void *arg);
static void add_parameter_name(PLpgSQL_nsitem_type itemtype, int itemno, const char *name);
static void add_dummy_return(PLpgSQL_function *function);
@@ -105,19 +91,6 @@ static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod,
Oid collation, TypeName *origtypname);
static void plpgsql_start_datums(void);
static void plpgsql_finish_datums(PLpgSQL_function *function);
-static void compute_function_hashkey(FunctionCallInfo fcinfo,
- Form_pg_proc procStruct,
- PLpgSQL_func_hashkey *hashkey,
- bool forValidator);
-static void plpgsql_resolve_polymorphic_argtypes(int numargs,
- Oid *argtypes, char *argmodes,
- Node *call_expr, bool forValidator,
- const char *proname);
-static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key);
-static void plpgsql_HashTableInsert(PLpgSQL_function *function,
- PLpgSQL_func_hashkey *func_key);
-static void plpgsql_HashTableDelete(PLpgSQL_function *function);
-static void delete_function(PLpgSQL_function *func);
/* ----------
* plpgsql_compile Make an execution tree for a PL/pgSQL function.
@@ -132,97 +105,24 @@ static void delete_function(PLpgSQL_function *func);
PLpgSQL_function *
plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
{
- Oid funcOid = fcinfo->flinfo->fn_oid;
- HeapTuple procTup;
- Form_pg_proc procStruct;
PLpgSQL_function *function;
- PLpgSQL_func_hashkey hashkey;
- bool function_valid = false;
- bool hashkey_valid = false;
/*
- * Lookup the pg_proc tuple by Oid; we'll need it in any case
+ * funccache.c manages re-use of existing PLpgSQL_function caches.
+ *
+ * In PL/pgSQL we use fn_extra directly as the pointer to the long-lived
+ * function cache entry; we have no need for any query-lifespan cache.
+ * Also, we don't need to make the cache key depend on composite result
+ * type (at least for now).
*/
- procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
- if (!HeapTupleIsValid(procTup))
- elog(ERROR, "cache lookup failed for function %u", funcOid);
- procStruct = (Form_pg_proc) GETSTRUCT(procTup);
-
- /*
- * See if there's already a cache entry for the current FmgrInfo. If not,
- * try to find one in the hash table.
- */
- function = (PLpgSQL_function *) fcinfo->flinfo->fn_extra;
-
-recheck:
- if (!function)
- {
- /* Compute hashkey using function signature and actual arg types */
- compute_function_hashkey(fcinfo, procStruct, &hashkey, forValidator);
- hashkey_valid = true;
-
- /* And do the lookup */
- function = plpgsql_HashTableLookup(&hashkey);
- }
-
- if (function)
- {
- /* We have a compiled function, but is it still valid? */
- if (function->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
- ItemPointerEquals(&function->fn_tid, &procTup->t_self))
- function_valid = true;
- else
- {
- /*
- * Nope, so remove it from hashtable and try to drop associated
- * storage (if not done already).
- */
- delete_function(function);
-
- /*
- * If the function isn't in active use then we can overwrite the
- * func struct with new data, allowing any other existing fn_extra
- * pointers to make use of the new definition on their next use.
- * If it is in use then just leave it alone and make a new one.
- * (The active invocations will run to completion using the
- * previous definition, and then the cache entry will just be
- * leaked; doesn't seem worth adding code to clean it up, given
- * what a corner case this is.)
- *
- * If we found the function struct via fn_extra then it's possible
- * a replacement has already been made, so go back and recheck the
- * hashtable.
- */
- if (function->use_count != 0)
- {
- function = NULL;
- if (!hashkey_valid)
- goto recheck;
- }
- }
- }
-
- /*
- * If the function wasn't found or was out-of-date, we have to compile it
- */
- if (!function_valid)
- {
- /*
- * Calculate hashkey if we didn't already; we'll need it to store the
- * completed function.
- */
- if (!hashkey_valid)
- compute_function_hashkey(fcinfo, procStruct, &hashkey,
- forValidator);
-
- /*
- * Do the hard part.
- */
- function = do_compile(fcinfo, procTup, function,
- &hashkey, forValidator);
- }
-
- ReleaseSysCache(procTup);
+ function = (PLpgSQL_function *)
+ cached_function_compile(fcinfo,
+ fcinfo->flinfo->fn_extra,
+ plpgsql_compile_callback,
+ plpgsql_delete_callback,
+ sizeof(PLpgSQL_function),
+ false,
+ forValidator);
/*
* Save pointer in FmgrInfo to avoid search on subsequent calls
@@ -244,8 +144,8 @@ struct compile_error_callback_arg
/*
* This is the slow part of plpgsql_compile().
*
- * The passed-in "function" pointer is either NULL or an already-allocated
- * function struct to overwrite.
+ * The passed-in "cfunc" struct is expected to be zeroes, except
+ * for the CachedFunction fields, which we don't touch here.
*
* While compiling a function, the CurrentMemoryContext is the
* per-function memory context of the function we are compiling. That
@@ -263,13 +163,14 @@ struct compile_error_callback_arg
* NB: this code is not re-entrant. We assume that nothing we do here could
* result in the invocation of another plpgsql function.
*/
-static PLpgSQL_function *
-do_compile(FunctionCallInfo fcinfo,
- HeapTuple procTup,
- PLpgSQL_function *function,
- PLpgSQL_func_hashkey *hashkey,
- bool forValidator)
+static void
+plpgsql_compile_callback(FunctionCallInfo fcinfo,
+ HeapTuple procTup,
+ const CachedFunctionHashKey *hashkey,
+ CachedFunction *cfunc,
+ bool forValidator)
{
+ PLpgSQL_function *function = (PLpgSQL_function *) cfunc;
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
bool is_dml_trigger = CALLED_AS_TRIGGER(fcinfo);
bool is_event_trigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
@@ -320,21 +221,6 @@ do_compile(FunctionCallInfo fcinfo,
* reasons.
*/
plpgsql_check_syntax = forValidator;
-
- /*
- * Create the new function struct, if not done already. The function
- * structs are never thrown away, so keep them in TopMemoryContext.
- */
- if (function == NULL)
- {
- function = (PLpgSQL_function *)
- MemoryContextAllocZero(TopMemoryContext, sizeof(PLpgSQL_function));
- }
- else
- {
- /* re-using a previously existing struct, so clear it out */
- memset(function, 0, sizeof(PLpgSQL_function));
- }
plpgsql_curr_compile = function;
/*
@@ -349,8 +235,6 @@ do_compile(FunctionCallInfo fcinfo,
function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
MemoryContextSetIdentifier(func_cxt, function->fn_signature);
function->fn_oid = fcinfo->flinfo->fn_oid;
- function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
- function->fn_tid = procTup->t_self;
function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
@@ -400,10 +284,10 @@ do_compile(FunctionCallInfo fcinfo,
numargs = get_func_arg_info(procTup,
&argtypes, &argnames, &argmodes);
- plpgsql_resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
- fcinfo->flinfo->fn_expr,
- forValidator,
- plpgsql_error_funcname);
+ cfunc_resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
+ fcinfo->flinfo->fn_expr,
+ forValidator,
+ plpgsql_error_funcname);
in_arg_varnos = (int *) palloc(numargs * sizeof(int));
out_arg_variables = (PLpgSQL_variable **) palloc(numargs * sizeof(PLpgSQL_variable *));
@@ -819,11 +703,6 @@ do_compile(FunctionCallInfo fcinfo,
if (plpgsql_DumpExecTree)
plpgsql_dumptree(function);
- /*
- * add it to the hash table
- */
- plpgsql_HashTableInsert(function, hashkey);
-
/*
* Pop the error context stack
*/
@@ -834,14 +713,13 @@ do_compile(FunctionCallInfo fcinfo,
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
plpgsql_compile_tmp_cxt = NULL;
- return function;
}
/* ----------
* plpgsql_compile_inline Make an execution tree for an anonymous code block.
*
- * Note: this is generally parallel to do_compile(); is it worth trying to
- * merge the two?
+ * Note: this is generally parallel to plpgsql_compile_callback(); is it worth
+ * trying to merge the two?
*
* Note: we assume the block will be thrown away so there is no need to build
* persistent data structures.
@@ -2437,242 +2315,3 @@ plpgsql_add_initdatums(int **varnos)
datums_last = plpgsql_nDatums;
return n;
}
-
-
-/*
- * Compute the hashkey for a given function invocation
- *
- * The hashkey is returned into the caller-provided storage at *hashkey.
- */
-static void
-compute_function_hashkey(FunctionCallInfo fcinfo,
- Form_pg_proc procStruct,
- PLpgSQL_func_hashkey *hashkey,
- bool forValidator)
-{
- /* Make sure any unused bytes of the struct are zero */
- MemSet(hashkey, 0, sizeof(PLpgSQL_func_hashkey));
-
- /* get function OID */
- hashkey->funcOid = fcinfo->flinfo->fn_oid;
-
- /* get call context */
- hashkey->isTrigger = CALLED_AS_TRIGGER(fcinfo);
- hashkey->isEventTrigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
-
- /*
- * If DML trigger, include trigger's OID in the hash, so that each trigger
- * usage gets a different hash entry, allowing for e.g. different relation
- * rowtypes or transition table names. In validation mode we do not know
- * what relation or transition table names are intended to be used, so we
- * leave trigOid zero; the hash entry built in this case will never be
- * used for any actual calls.
- *
- * We don't currently need to distinguish different event trigger usages
- * in the same way, since the special parameter variables don't vary in
- * type in that case.
- */
- if (hashkey->isTrigger && !forValidator)
- {
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
-
- hashkey->trigOid = trigdata->tg_trigger->tgoid;
- }
-
- /* get input collation, if known */
- hashkey->inputCollation = fcinfo->fncollation;
-
- if (procStruct->pronargs > 0)
- {
- /* get the argument types */
- memcpy(hashkey->argtypes, procStruct->proargtypes.values,
- procStruct->pronargs * sizeof(Oid));
-
- /* resolve any polymorphic argument types */
- plpgsql_resolve_polymorphic_argtypes(procStruct->pronargs,
- hashkey->argtypes,
- NULL,
- fcinfo->flinfo->fn_expr,
- forValidator,
- NameStr(procStruct->proname));
- }
-}
-
-/*
- * This is the same as the standard resolve_polymorphic_argtypes() function,
- * except that:
- * 1. We go ahead and report the error if we can't resolve the types.
- * 2. We treat RECORD-type input arguments (not output arguments) as if
- * they were polymorphic, replacing their types with the actual input
- * types if we can determine those. This allows us to create a separate
- * function cache entry for each named composite type passed to such an
- * argument.
- * 3. In validation mode, we have no inputs to look at, so assume that
- * polymorphic arguments are integer, integer-array or integer-range.
- */
-static void
-plpgsql_resolve_polymorphic_argtypes(int numargs,
- Oid *argtypes, char *argmodes,
- Node *call_expr, bool forValidator,
- const char *proname)
-{
- int i;
-
- if (!forValidator)
- {
- int inargno;
-
- /* normal case, pass to standard routine */
- if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
- call_expr))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("could not determine actual argument "
- "type for polymorphic function \"%s\"",
- proname)));
- /* also, treat RECORD inputs (but not outputs) as polymorphic */
- inargno = 0;
- for (i = 0; i < numargs; i++)
- {
- char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
-
- if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
- continue;
- if (argtypes[i] == RECORDOID || argtypes[i] == RECORDARRAYOID)
- {
- Oid resolvedtype = get_call_expr_argtype(call_expr,
- inargno);
-
- if (OidIsValid(resolvedtype))
- argtypes[i] = resolvedtype;
- }
- inargno++;
- }
- }
- else
- {
- /* special validation case (no need to do anything for RECORD) */
- for (i = 0; i < numargs; i++)
- {
- switch (argtypes[i])
- {
- case ANYELEMENTOID:
- case ANYNONARRAYOID:
- case ANYENUMOID: /* XXX dubious */
- case ANYCOMPATIBLEOID:
- case ANYCOMPATIBLENONARRAYOID:
- argtypes[i] = INT4OID;
- break;
- case ANYARRAYOID:
- case ANYCOMPATIBLEARRAYOID:
- argtypes[i] = INT4ARRAYOID;
- break;
- case ANYRANGEOID:
- case ANYCOMPATIBLERANGEOID:
- argtypes[i] = INT4RANGEOID;
- break;
- case ANYMULTIRANGEOID:
- argtypes[i] = INT4MULTIRANGEOID;
- break;
- default:
- break;
- }
- }
- }
-}
-
-/*
- * delete_function - clean up as much as possible of a stale function cache
- *
- * We can't release the PLpgSQL_function struct itself, because of the
- * possibility that there are fn_extra pointers to it. We can release
- * the subsidiary storage, but only if there are no active evaluations
- * in progress. Otherwise we'll just leak that storage. Since the
- * case would only occur if a pg_proc update is detected during a nested
- * recursive call on the function, a leak seems acceptable.
- *
- * Note that this can be called more than once if there are multiple fn_extra
- * pointers to the same function cache. Hence be careful not to do things
- * twice.
- */
-static void
-delete_function(PLpgSQL_function *func)
-{
- /* remove function from hash table (might be done already) */
- plpgsql_HashTableDelete(func);
-
- /* release the function's storage if safe and not done already */
- if (func->use_count == 0)
- plpgsql_free_function_memory(func);
-}
-
-/* exported so we can call it from _PG_init() */
-void
-plpgsql_HashTableInit(void)
-{
- HASHCTL ctl;
-
- /* don't allow double-initialization */
- Assert(plpgsql_HashTable == NULL);
-
- ctl.keysize = sizeof(PLpgSQL_func_hashkey);
- ctl.entrysize = sizeof(plpgsql_HashEnt);
- plpgsql_HashTable = hash_create("PLpgSQL function hash",
- FUNCS_PER_USER,
- &ctl,
- HASH_ELEM | HASH_BLOBS);
-}
-
-static PLpgSQL_function *
-plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key)
-{
- plpgsql_HashEnt *hentry;
-
- hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
- func_key,
- HASH_FIND,
- NULL);
- if (hentry)
- return hentry->function;
- else
- return NULL;
-}
-
-static void
-plpgsql_HashTableInsert(PLpgSQL_function *function,
- PLpgSQL_func_hashkey *func_key)
-{
- plpgsql_HashEnt *hentry;
- bool found;
-
- hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
- func_key,
- HASH_ENTER,
- &found);
- if (found)
- elog(WARNING, "trying to insert a function that already exists");
-
- hentry->function = function;
- /* prepare back link from function to hashtable key */
- function->fn_hashkey = &hentry->key;
-}
-
-static void
-plpgsql_HashTableDelete(PLpgSQL_function *function)
-{
- plpgsql_HashEnt *hentry;
-
- /* do nothing if not in table */
- if (function->fn_hashkey == NULL)
- return;
-
- hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
- function->fn_hashkey,
- HASH_REMOVE,
- NULL);
- if (hentry == NULL)
- elog(WARNING, "trying to delete function that does not exist");
-
- /* remove back link, which no longer points to allocated storage */
- function->fn_hashkey = NULL;
-}
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index 6b5394fc5fa..bc7a61feb4d 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -718,7 +718,7 @@ plpgsql_free_function_memory(PLpgSQL_function *func)
int i;
/* Better not call this on an in-use function */
- Assert(func->use_count == 0);
+ Assert(func->cfunc.use_count == 0);
/* Release plans associated with variable declarations */
for (i = 0; i < func->ndatums; i++)
@@ -767,6 +767,13 @@ plpgsql_free_function_memory(PLpgSQL_function *func)
func->fn_cxt = NULL;
}
+/* Deletion callback used by funccache.c */
+void
+plpgsql_delete_callback(CachedFunction *cfunc)
+{
+ plpgsql_free_function_memory((PLpgSQL_function *) cfunc);
+}
+
/**********************************************************************
* Debug functions for analyzing the compiled code
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 1bf12232862..e9a72929947 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -202,7 +202,6 @@ _PG_init(void)
MarkGUCPrefixReserved("plpgsql");
- plpgsql_HashTableInit();
RegisterXactCallback(plpgsql_xact_cb, NULL);
RegisterSubXactCallback(plpgsql_subxact_cb, NULL);
@@ -247,7 +246,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
save_cur_estate = func->cur_estate;
/* Mark the function as busy, so it can't be deleted from under us */
- func->use_count++;
+ func->cfunc.use_count++;
/*
* If we'll need a procedure-lifespan resowner to execute any CALL or DO
@@ -284,7 +283,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
PG_FINALLY();
{
/* Decrement use-count, restore cur_estate */
- func->use_count--;
+ func->cfunc.use_count--;
func->cur_estate = save_cur_estate;
/* Be sure to release the procedure resowner if any */
@@ -334,7 +333,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
func = plpgsql_compile_inline(codeblock->source_text);
/* Mark the function as busy, just pro forma */
- func->use_count++;
+ func->cfunc.use_count++;
/*
* Set up a fake fcinfo with just enough info to satisfy
@@ -398,8 +397,8 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
ResourceOwnerDelete(simple_eval_resowner);
/* Function should now have no remaining use-counts ... */
- func->use_count--;
- Assert(func->use_count == 0);
+ func->cfunc.use_count--;
+ Assert(func->cfunc.use_count == 0);
/* ... so we can free subsidiary storage */
plpgsql_free_function_memory(func);
@@ -415,8 +414,8 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
ResourceOwnerDelete(simple_eval_resowner);
/* Function should now have no remaining use-counts ... */
- func->use_count--;
- Assert(func->use_count == 0);
+ func->cfunc.use_count--;
+ Assert(func->cfunc.use_count == 0);
/* ... so we can free subsidiary storage */
plpgsql_free_function_memory(func);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index b67847b5111..41e52b8ce71 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -21,6 +21,7 @@
#include "commands/trigger.h"
#include "executor/spi.h"
#include "utils/expandedrecord.h"
+#include "utils/funccache.h"
#include "utils/typcache.h"
@@ -941,40 +942,6 @@ typedef struct PLpgSQL_stmt_dynexecute
List *params; /* USING expressions */
} PLpgSQL_stmt_dynexecute;
-/*
- * Hash lookup key for functions
- */
-typedef struct PLpgSQL_func_hashkey
-{
- Oid funcOid;
-
- bool isTrigger; /* true if called as a DML trigger */
- bool isEventTrigger; /* true if called as an event trigger */
-
- /* be careful that pad bytes in this struct get zeroed! */
-
- /*
- * For a trigger function, the OID of the trigger is part of the hash key
- * --- we want to compile the trigger function separately for each trigger
- * it is used with, in case the rowtype or transition table names are
- * different. Zero if not called as a DML trigger.
- */
- Oid trigOid;
-
- /*
- * We must include the input collation as part of the hash key too,
- * because we have to generate different plans (with different Param
- * collations) for different collation settings.
- */
- Oid inputCollation;
-
- /*
- * We include actual argument types in the hash key to support polymorphic
- * PLpgSQL functions. Be careful that extra positions are zeroed!
- */
- Oid argtypes[FUNC_MAX_ARGS];
-} PLpgSQL_func_hashkey;
-
/*
* Trigger type
*/
@@ -990,13 +957,12 @@ typedef enum PLpgSQL_trigtype
*/
typedef struct PLpgSQL_function
{
+ CachedFunction cfunc; /* fields managed by funccache.c */
+
char *fn_signature;
Oid fn_oid;
- TransactionId fn_xmin;
- ItemPointerData fn_tid;
PLpgSQL_trigtype fn_is_trigger;
Oid fn_input_collation;
- PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt;
Oid fn_rettype;
@@ -1036,9 +1002,8 @@ typedef struct PLpgSQL_function
bool requires_procedure_resowner; /* contains CALL or DO? */
bool has_exception_block; /* contains BEGIN...EXCEPTION? */
- /* these fields change when the function is used */
+ /* this field changes when the function is used */
struct PLpgSQL_execstate *cur_estate;
- unsigned long use_count;
} PLpgSQL_function;
/*
@@ -1287,7 +1252,6 @@ extern PGDLLEXPORT int plpgsql_recognize_err_condition(const char *condname,
extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
extern void plpgsql_adddatum(PLpgSQL_datum *newdatum);
extern int plpgsql_add_initdatums(int **varnos);
-extern void plpgsql_HashTableInit(void);
/*
* Functions in pl_exec.c
@@ -1335,6 +1299,7 @@ extern PGDLLEXPORT const char *plpgsql_stmt_typename(PLpgSQL_stmt *stmt);
extern const char *plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind kind);
extern void plpgsql_mark_local_assignment_targets(PLpgSQL_function *func);
extern void plpgsql_free_function_memory(PLpgSQL_function *func);
+extern void plpgsql_delete_callback(CachedFunction *cfunc);
extern void plpgsql_dumptree(PLpgSQL_function *func);
/*
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index d5388a1fecf..72bae1bf254 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -651,7 +651,7 @@ LINE 1: SELECT public.dep_req2() || ' req3b'
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT public.dep_req2() || ' req3b'
-CONTEXT: SQL function "dep_req3b" during startup
+CONTEXT: SQL function "dep_req3b" statement 1
DROP EXTENSION test_ext_req_schema3;
ALTER EXTENSION test_ext_req_schema1 SET SCHEMA test_s_dep2; -- now ok
SELECT test_s_dep2.dep_req1();
diff --git a/src/test/regress/expected/create_function_sql.out b/src/test/regress/expected/create_function_sql.out
index 50aca5940ff..2ee7631044e 100644
--- a/src/test/regress/expected/create_function_sql.out
+++ b/src/test/regress/expected/create_function_sql.out
@@ -563,6 +563,20 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- early shutdown of set-returning functions
+CREATE FUNCTION functest_srf0() RETURNS SETOF int
+LANGUAGE SQL
+AS $$ SELECT i FROM generate_series(1, 100) i $$;
+SELECT functest_srf0() LIMIT 5;
+ functest_srf0
+---------------
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
-- inlining of set-returning functions
CREATE TABLE functest3 (a int);
INSERT INTO functest3 VALUES (1), (2), (3);
@@ -666,6 +680,43 @@ SELECT * FROM voidtest5(3);
-----------
(0 rows)
+-- DDL within a SQL function can now affect later statements in the function;
+-- though that doesn't work if check_function_bodies is on.
+SET check_function_bodies TO off;
+CREATE FUNCTION create_and_insert() RETURNS VOID LANGUAGE sql AS $$
+ create table ddl_test (f1 int);
+ insert into ddl_test values (1.2);
+$$;
+SELECT create_and_insert();
+ create_and_insert
+-------------------
+
+(1 row)
+
+TABLE ddl_test;
+ f1
+----
+ 1
+(1 row)
+
+CREATE FUNCTION alter_and_insert() RETURNS VOID LANGUAGE sql AS $$
+ alter table ddl_test alter column f1 type numeric;
+ insert into ddl_test values (1.2);
+$$;
+SELECT alter_and_insert();
+ alter_and_insert
+------------------
+
+(1 row)
+
+TABLE ddl_test;
+ f1
+-----
+ 1
+ 1.2
+(2 rows)
+
+RESET check_function_bodies;
-- Regression tests for bugs:
-- Check that arguments that are R/W expanded datums aren't corrupted by
-- multiple uses. This test knows that array_append() returns a R/W datum
@@ -708,7 +759,7 @@ CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
ERROR: only one AS item needed for language "sql"
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 30 other objects
+NOTICE: drop cascades to 34 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -732,12 +783,16 @@ drop cascades to function functest_s_10(text,date)
drop cascades to function functest_s_13()
drop cascades to function functest_s_15(integer)
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_srf0()
drop cascades to function functest_sri1()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
drop cascades to function voidtest4(integer)
drop cascades to function voidtest5(integer)
+drop cascades to function create_and_insert()
+drop cascades to table ddl_test
+drop cascades to function alter_and_insert()
drop cascades to function double_append(anyarray,anyelement)
DROP USER regress_unpriv_user;
RESET search_path;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 397a8b35d6d..c21be83aa4a 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1885,7 +1885,7 @@ select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
select * from array_to_set(array['one', 'two']) as t(f1 point,f2 text);
ERROR: return type mismatch in function declared to return record
DETAIL: Final statement returns integer instead of point at column 1.
-CONTEXT: SQL function "array_to_set" during startup
+CONTEXT: SQL function "array_to_set" statement 1
-- with "strict", this function can't be inlined in FROM
explain (verbose, costs off)
select * from array_to_set(array['one', 'two']) as t(f1 numeric(4,2),f2 text);
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 87929191d06..1c4e37d2249 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -4695,6 +4695,57 @@ RESET ROLE;
DROP FUNCTION rls_f();
DROP VIEW rls_v;
DROP TABLE rls_t;
+-- Check that RLS changes invalidate SQL function plans
+create table rls_t (c text);
+create table test_t (c text);
+insert into rls_t values ('a'), ('b'), ('c'), ('d');
+insert into test_t values ('a'), ('b');
+alter table rls_t enable row level security;
+grant select on rls_t to regress_rls_alice;
+grant select on test_t to regress_rls_alice;
+create policy p1 on rls_t for select to regress_rls_alice
+ using (c = current_setting('rls_test.blah'));
+-- Function changes row_security setting and so invalidates plan
+create function rls_f(text) returns text
+begin atomic
+ select set_config('rls_test.blah', $1, true) || set_config('row_security', 'false', true) || string_agg(c, ',' order by c) from rls_t;
+end;
+set plan_cache_mode to force_custom_plan;
+-- Table owner bypasses RLS
+select rls_f(c) from test_t order by rls_f;
+ rls_f
+-------------
+ aoffa,b,c,d
+ boffa,b,c,d
+(2 rows)
+
+-- For other users, changes in row_security setting
+-- should lead to RLS error during query rewrite
+set role regress_rls_alice;
+select rls_f(c) from test_t order by rls_f;
+ERROR: query would be affected by row-level security policy for table "rls_t"
+CONTEXT: SQL function "rls_f" statement 1
+reset role;
+set plan_cache_mode to force_generic_plan;
+-- Table owner bypasses RLS, although cached plan will be invalidated
+select rls_f(c) from test_t order by rls_f;
+ rls_f
+-------------
+ aoffa,b,c,d
+ boffa,b,c,d
+(2 rows)
+
+-- For other users, changes in row_security setting
+-- should lead to plan invalidation and RLS error during query rewrite
+set role regress_rls_alice;
+select rls_f(c) from test_t order by rls_f;
+ERROR: query would be affected by row-level security policy for table "rls_t"
+CONTEXT: SQL function "rls_f" statement 1
+reset role;
+reset plan_cache_mode;
+reset rls_test.blah;
+drop function rls_f(text);
+drop table rls_t, test_t;
--
-- Clean up objects
--
diff --git a/src/test/regress/sql/create_function_sql.sql b/src/test/regress/sql/create_function_sql.sql
index 89e9af3a499..68776be4c8d 100644
--- a/src/test/regress/sql/create_function_sql.sql
+++ b/src/test/regress/sql/create_function_sql.sql
@@ -328,6 +328,15 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- early shutdown of set-returning functions
+
+CREATE FUNCTION functest_srf0() RETURNS SETOF int
+LANGUAGE SQL
+AS $$ SELECT i FROM generate_series(1, 100) i $$;
+
+SELECT functest_srf0() LIMIT 5;
+
+
-- inlining of set-returning functions
CREATE TABLE functest3 (a int);
@@ -385,6 +394,31 @@ CREATE FUNCTION voidtest5(a int) RETURNS SETOF VOID LANGUAGE SQL AS
$$ SELECT generate_series(1, a) $$ STABLE;
SELECT * FROM voidtest5(3);
+-- DDL within a SQL function can now affect later statements in the function;
+-- though that doesn't work if check_function_bodies is on.
+
+SET check_function_bodies TO off;
+
+CREATE FUNCTION create_and_insert() RETURNS VOID LANGUAGE sql AS $$
+ create table ddl_test (f1 int);
+ insert into ddl_test values (1.2);
+$$;
+
+SELECT create_and_insert();
+
+TABLE ddl_test;
+
+CREATE FUNCTION alter_and_insert() RETURNS VOID LANGUAGE sql AS $$
+ alter table ddl_test alter column f1 type numeric;
+ insert into ddl_test values (1.2);
+$$;
+
+SELECT alter_and_insert();
+
+TABLE ddl_test;
+
+RESET check_function_bodies;
+
-- Regression tests for bugs:
-- Check that arguments that are R/W expanded datums aren't corrupted by
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index f61dbbf9581..9da967a9ef2 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -2307,6 +2307,50 @@ DROP FUNCTION rls_f();
DROP VIEW rls_v;
DROP TABLE rls_t;
+-- Check that RLS changes invalidate SQL function plans
+create table rls_t (c text);
+create table test_t (c text);
+insert into rls_t values ('a'), ('b'), ('c'), ('d');
+insert into test_t values ('a'), ('b');
+alter table rls_t enable row level security;
+grant select on rls_t to regress_rls_alice;
+grant select on test_t to regress_rls_alice;
+create policy p1 on rls_t for select to regress_rls_alice
+ using (c = current_setting('rls_test.blah'));
+
+-- Function changes row_security setting and so invalidates plan
+create function rls_f(text) returns text
+begin atomic
+ select set_config('rls_test.blah', $1, true) || set_config('row_security', 'false', true) || string_agg(c, ',' order by c) from rls_t;
+end;
+
+set plan_cache_mode to force_custom_plan;
+
+-- Table owner bypasses RLS
+select rls_f(c) from test_t order by rls_f;
+
+-- For other users, changes in row_security setting
+-- should lead to RLS error during query rewrite
+set role regress_rls_alice;
+select rls_f(c) from test_t order by rls_f;
+reset role;
+
+set plan_cache_mode to force_generic_plan;
+
+-- Table owner bypasses RLS, although cached plan will be invalidated
+select rls_f(c) from test_t order by rls_f;
+
+-- For other users, changes in row_security setting
+-- should lead to plan invalidation and RLS error during query rewrite
+set role regress_rls_alice;
+select rls_f(c) from test_t order by rls_f;
+reset role;
+
+reset plan_cache_mode;
+reset rls_test.blah;
+drop function rls_f(text);
+drop table rls_t, test_t;
+
--
-- Clean up objects
--
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 449bafc123c..8f28d8ff28e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -382,6 +382,11 @@ CURLM
CURLoption
CV
CachedExpression
+CachedFunction
+CachedFunctionCompileCallback
+CachedFunctionDeleteCallback
+CachedFunctionHashEntry
+CachedFunctionHashKey
CachedPlan
CachedPlanSource
CallContext
@@ -2267,6 +2272,7 @@ PortalHashEnt
PortalStatus
PortalStrategy
PostParseColumnRefHook
+PostRewriteHook
PostgresPollingStatusType
PostingItem
PreParseColumnRefHook
@@ -2608,6 +2614,8 @@ SPPageDesc
SQLDropObject
SQLFunctionCache
SQLFunctionCachePtr
+SQLFunctionHashEntry
+SQLFunctionLink
SQLFunctionParseInfo
SQLFunctionParseInfoPtr
SQLValueFunction