0003-Introduce-SQL-functions-plan-cache.patch
text/x-diff
Filename: 0003-Introduce-SQL-functions-plan-cache.patch
Type: text/x-diff
Part: 1
From 7258bf8c6f8c443d9b922ebac306ca279891e6a6 Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Date: Fri, 7 Feb 2025 11:51:24 +0300
Subject: [PATCH 3/4] Introduce SQL functions plan cache
---
src/backend/executor/functions.c | 658 ++++++++++++++----
.../expected/test_extensions.out | 2 +-
2 files changed, 524 insertions(+), 136 deletions(-)
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 9aa5e0def46..06efcec4e7c 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -18,6 +18,8 @@
#include "access/xact.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "commands/event_trigger.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
@@ -137,6 +139,45 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
+/*
+ * Plan cache-related structures
+ */
+typedef struct SQLFunctionPlanKey
+{
+ Oid fn_oid;
+ Oid inputCollation;
+ Oid argtypes[FUNC_MAX_ARGS];
+} SQLFunctionPlanKey;
+
+typedef struct SQLFunctionPlanEntry
+{
+ SQLFunctionPlanKey key;
+
+ /* Fields required to invalidate a cache entry */
+ TransactionId fn_xmin;
+ ItemPointerData fn_tid;
+
+ /*
+ * result_tlist is required to recreate function execution state as well
+ * as to validate a cache entry
+ */
+ List *result_tlist;
+
+ bool returnsTuple; /* True if this function returns tuple */
+ List *plansource_list; /* List of CachedPlanSource for this
+ * function */
+
+ /*
+ * SQLFunctionParseInfoPtr is used as hooks arguments, so should persist
+ * across calls. Fortunately, if it doesn't, this means that argtypes or
+ * collation mismatches and we get new cache entry.
+ */
+ SQLFunctionParseInfoPtr pinfo; /* cached information about arguments */
+
+ MemoryContext entry_ctx; /* memory context for allocated fields of this entry */
+} SQLFunctionPlanEntry;
+
+static HTAB *sql_plan_cache_htab = NULL;
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -170,6 +211,48 @@ static bool sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self);
static void sqlfunction_shutdown(DestReceiver *self);
static void sqlfunction_destroy(DestReceiver *self);
+/* SQL-functions plan cache-related routines */
+static void compute_plan_entry_key(SQLFunctionPlanKey *hashkey, FunctionCallInfo fcinfo, Form_pg_proc procedureStruct);
+static SQLFunctionPlanEntry *get_cached_plan_entry(SQLFunctionPlanKey *hashkey);
+static void save_cached_plan_entry(SQLFunctionPlanKey * hashkey, HeapTuple procedureTuple, List *plansource_list, List *result_tlist, bool returnsTuple, SQLFunctionParseInfoPtr pinfo, MemoryContext alianable_context);
+static void delete_cached_plan_entry(SQLFunctionPlanEntry * entry);
+
+static bool check_sql_fn_retval_matches(List *tlist, Oid rettype, TupleDesc rettupdesc, char prokind);
+static bool target_entry_has_compatible_type(TargetEntry *tle, Oid res_type, int32 res_typmod);
+
+/*
+ * Fill array of arguments with actual function argument types oids
+ */
+static void
+compute_argument_types(Oid *argOidVect, Form_pg_proc procedureStruct, Node *call_expr)
+{
+ int argnum;
+ int nargs;
+
+ nargs = procedureStruct->pronargs;
+ if (nargs > 0)
+ {
+ memcpy(argOidVect,
+ procedureStruct->proargtypes.values,
+ nargs * sizeof(Oid));
+
+ for (argnum = 0; argnum < nargs; argnum++)
+ {
+ Oid argtype = argOidVect[argnum];
+
+ if (IsPolymorphicType(argtype))
+ {
+ argtype = get_call_expr_argtype(call_expr, argnum);
+ if (argtype == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not determine actual type of argument declared %s",
+ format_type_be(argOidVect[argnum]))));
+ argOidVect[argnum] = argtype;
+ }
+ }
+ }
+}
/*
* Prepare the SQLFunctionParseInfo struct for parsing a SQL function body
@@ -203,31 +286,8 @@ prepare_sql_fn_parse_info(HeapTuple procedureTuple,
pinfo->nargs = nargs = procedureStruct->pronargs;
if (nargs > 0)
{
- Oid *argOidVect;
- int argnum;
-
- argOidVect = (Oid *) palloc(nargs * sizeof(Oid));
- memcpy(argOidVect,
- procedureStruct->proargtypes.values,
- nargs * sizeof(Oid));
-
- for (argnum = 0; argnum < nargs; argnum++)
- {
- Oid argtype = argOidVect[argnum];
-
- if (IsPolymorphicType(argtype))
- {
- argtype = get_call_expr_argtype(call_expr, argnum);
- if (argtype == InvalidOid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("could not determine actual type of argument declared %s",
- format_type_be(argOidVect[argnum]))));
- argOidVect[argnum] = argtype;
- }
- }
-
- pinfo->argtypes = argOidVect;
+ pinfo->argtypes = (Oid *) palloc(nargs * sizeof(Oid));
+ compute_argument_types(pinfo->argtypes, procedureStruct, call_expr);
}
/*
@@ -605,6 +665,264 @@ init_execution_state(SQLFunctionCachePtr fcache,
return eslist;
}
+/*
+ * Compute key for searching plan entry in backend cache
+ */
+static void
+compute_plan_entry_key(SQLFunctionPlanKey * hashkey, FunctionCallInfo fcinfo, Form_pg_proc procedureStruct)
+{
+ MemSet(hashkey, 0, sizeof(SQLFunctionPlanKey));
+
+ hashkey->fn_oid = fcinfo->flinfo->fn_oid;
+
+ /* set input collation, if known */
+ hashkey->inputCollation = fcinfo->fncollation;
+
+ if (procedureStruct->pronargs > 0)
+ {
+ /* get the argument types */
+ compute_argument_types(hashkey->argtypes, procedureStruct, fcinfo->flinfo->fn_expr);
+ }
+}
+
+/*
+ * Get cached plan by pre-computed key
+ */
+static SQLFunctionPlanEntry *
+get_cached_plan_entry(SQLFunctionPlanKey * hashkey)
+{
+ SQLFunctionPlanEntry *plan_entry = NULL;
+
+ if (sql_plan_cache_htab)
+ {
+ plan_entry = (SQLFunctionPlanEntry *) hash_search(sql_plan_cache_htab,
+ hashkey,
+ HASH_FIND,
+ NULL);
+ }
+ return plan_entry;
+}
+
+/*
+ * Save function execution plan in cache
+ */
+static void
+save_cached_plan_entry(SQLFunctionPlanKey * hashkey, HeapTuple procedureTuple, List *plansource_list, List *result_tlist, bool returnsTuple, SQLFunctionParseInfoPtr pinfo, MemoryContext alianable_context)
+{
+ MemoryContext oldcontext;
+ MemoryContext entry_context;
+ SQLFunctionPlanEntry *entry;
+ ListCell *lc;
+ bool found;
+
+ if (sql_plan_cache_htab == NULL)
+ {
+ HASHCTL ctl;
+
+ ctl.keysize = sizeof(SQLFunctionPlanKey);
+ ctl.entrysize = sizeof(SQLFunctionPlanEntry);
+ ctl.hcxt = CacheMemoryContext;
+
+ sql_plan_cache_htab = hash_create("SQL function plan hash",
+ 100 /* arbitrary initial size */ ,
+ &ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ }
+
+ entry = (SQLFunctionPlanEntry *) hash_search(sql_plan_cache_htab,
+ hashkey,
+ HASH_ENTER,
+ &found);
+ if (found)
+ elog(WARNING, "trying to insert a function that already exists");
+
+ /*
+ * Create long-lived memory context that holds entry fields
+ */
+ entry_context = AllocSetContextCreate(CacheMemoryContext,
+ "SQL function plan entry context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ oldcontext = MemoryContextSwitchTo(entry_context);
+
+ /* fill entry */
+ memcpy(&entry->key, hashkey, sizeof(SQLFunctionPlanKey));
+
+ entry->entry_ctx = entry_context;
+
+ /* Some generated data, like pinfo, should be reparented */
+ MemoryContextSetParent(alianable_context, entry->entry_ctx);
+
+ entry->pinfo = pinfo;
+
+ /* Preserve list in long-lived context */
+ if (plansource_list)
+ entry->plansource_list = list_copy(plansource_list);
+ else
+ entry->plansource_list = NULL;
+
+ entry->result_tlist = copyObject(result_tlist);
+
+ entry->returnsTuple = returnsTuple;
+
+ /* Fill fields needed to invalidate cache entry */
+ entry->fn_xmin = HeapTupleHeaderGetRawXmin(procedureTuple->t_data);
+ entry->fn_tid = procedureTuple->t_self;
+
+ /* Save plans */
+ foreach(lc, entry->plansource_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ SaveCachedPlan(plansource);
+ }
+ MemoryContextSwitchTo(oldcontext);
+
+}
+
+/*
+ * Remove plan from cache
+ */
+static void
+delete_cached_plan_entry(SQLFunctionPlanEntry * entry)
+{
+ ListCell *lc;
+ bool found;
+
+ /* Release plans */
+ foreach(lc, entry->plansource_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ DropCachedPlan(plansource);
+ }
+ MemoryContextDelete(entry->entry_ctx);
+
+ hash_search(sql_plan_cache_htab, &entry->key, HASH_REMOVE, &found);
+ Assert(found);
+}
+
+/*
+ * Determine if TargetEntry is compatible to specified type
+ */
+static bool
+target_entry_has_compatible_type(TargetEntry *tle, Oid res_type, int32 res_typmod)
+{
+ Var *var;
+ Node *cast_result;
+ bool result = true;
+
+ /* Are types equivalent? */
+ var = makeVarFromTargetEntry(1, tle);
+
+ cast_result = coerce_to_target_type(NULL,
+ (Node *) var,
+ var->vartype,
+ res_type, res_typmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ /*
+ * If conversion is not possible or requires a cast, entry is incompatible
+ * with the type.
+ */
+ if (cast_result == NULL || cast_result != (Node *) var)
+ result = false;
+
+ if (cast_result && cast_result != (Node *) var)
+ pfree(cast_result);
+ pfree(var);
+
+ return result;
+}
+
+/*
+ * Check if result tlist would be changed by check_sql_fn_retval()
+ */
+static bool
+check_sql_fn_retval_matches(List *tlist, Oid rettype, TupleDesc rettupdesc, char prokind)
+{
+ char fn_typtype;
+ int tlistlen;
+
+ /*
+ * Count the non-junk entries in the result targetlist.
+ */
+ tlistlen = ExecCleanTargetListLength(tlist);
+
+ fn_typtype = get_typtype(rettype);
+
+ if (fn_typtype == TYPTYPE_BASE ||
+ fn_typtype == TYPTYPE_DOMAIN ||
+ fn_typtype == TYPTYPE_ENUM ||
+ fn_typtype == TYPTYPE_RANGE ||
+ fn_typtype == TYPTYPE_MULTIRANGE)
+ {
+ TargetEntry *tle;
+
+ /* Something unexpected, invalidate cached plan */
+ if (tlistlen != 1)
+ return false;
+
+ tle = (TargetEntry *) linitial(tlist);
+
+ return target_entry_has_compatible_type(tle, rettype, -1);
+ }
+ else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
+ {
+ ListCell *lc;
+ int colindex;
+ int tupnatts;
+
+ if (tlistlen == 1 && prokind != PROKIND_PROCEDURE)
+ {
+ TargetEntry *tle = (TargetEntry *) linitial(tlist);
+
+ return target_entry_has_compatible_type(tle, rettype, -1);
+ }
+
+ /* We consider results comnpatible if there's no tupledesc */
+ if (rettupdesc == NULL)
+ return true;
+
+ /*
+ * Verify that saved targetlist matches the return tuple type.
+ */
+ tupnatts = rettupdesc->natts;
+ colindex = 0;
+ foreach(lc, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ Form_pg_attribute attr;
+
+ /* resjunk columns can simply be ignored */
+ if (tle->resjunk)
+ continue;
+
+ do
+ {
+ colindex++;
+ if (colindex > tupnatts)
+ return false;
+
+ attr = TupleDescAttr(rettupdesc, colindex - 1);
+ } while (attr->attisdropped);
+
+ if (!target_entry_has_compatible_type(tle, attr->atttypid, attr->atttypmod))
+ return false;
+ }
+
+ /* remaining columns in rettupdesc had better all be dropped */
+ for (colindex++; colindex <= tupnatts; colindex++)
+ {
+ if (!TupleDescCompactAttr(rettupdesc, colindex - 1)->attisdropped)
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* Initialize the SQLFunctionCache for a SQL function
*/
@@ -626,6 +944,10 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool *lazyEvalOK)
Datum tmp;
bool isNull;
List *plansource_list;
+ SQLFunctionPlanEntry *cached_plan_entry = NULL;
+ SQLFunctionPlanKey plan_cache_entry_key;
+ bool use_plan_cache;
+ bool plan_cache_entry_valid;
/*
* Create memory context that holds all the SQLFunctionCache data. It
@@ -683,15 +1005,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool *lazyEvalOK)
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.
*/
@@ -704,123 +1017,201 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool *lazyEvalOK)
Anum_pg_proc_prosqlbody,
&isNull);
+
+ use_plan_cache = true;
+ plan_cache_entry_valid = false;
+
/*
- * 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.
+ * If function is trigger, we can see different rowtypes or transition
+ * table names. So don't use cache for such plans.
*/
- queryTree_list = NIL;
- plansource_list = NIL;
- if (!isNull)
- {
- Node *n;
- List *stored_query_list;
+ if (CALLED_AS_TRIGGER(fcinfo) || CALLED_AS_EVENT_TRIGGER(fcinfo))
+ use_plan_cache = false;
- n = stringToNode(TextDatumGetCString(tmp));
- if (IsA(n, List))
- stored_query_list = linitial_node(List, castNode(List, n));
- else
- stored_query_list = list_make1(n);
+ if (use_plan_cache)
+ {
+ compute_plan_entry_key(&plan_cache_entry_key, fcinfo, procedureStruct);
- foreach(lc, stored_query_list)
+ cached_plan_entry = get_cached_plan_entry(&plan_cache_entry_key);
+ if (cached_plan_entry)
{
- Query *parsetree = lfirst_node(Query, lc);
- List *queryTree_sublist;
- CachedPlanSource *plansource;
-
- AcquireRewriteLocks(parsetree, true, false);
-
- plansource = CreateCachedPlanForQuery(parsetree, fcache->src, CreateCommandTag((Node *) parsetree));
- plansource_list = lappend(plansource_list, plansource);
-
- queryTree_sublist = pg_rewrite_query(parsetree);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ if (cached_plan_entry->fn_xmin == HeapTupleHeaderGetRawXmin(procedureTuple->t_data) &&
+ ItemPointerEquals(&cached_plan_entry->fn_tid, &procedureTuple->t_self))
+ {
+ /*
+ * Avoid using plan if returned result type doesn't match the
+ * expected one. check_sql_fn_retval() in this case would
+ * change query to match expected result type. But we've
+ * already planned query, possibly modified to match another
+ * result type. So discard the cached entry and replan.
+ */
+ if (check_sql_fn_retval_matches(cached_plan_entry->result_tlist, rettype, rettupdesc, procedureStruct->prokind))
+ plan_cache_entry_valid = true;
+ }
+ if (!plan_cache_entry_valid)
+ delete_cached_plan_entry(cached_plan_entry);
}
}
+
+ if (plan_cache_entry_valid)
+ {
+ plansource_list = cached_plan_entry->plansource_list;
+ resulttlist = copyObject(cached_plan_entry->result_tlist);
+ fcache->returnsTuple = cached_plan_entry->returnsTuple;
+ fcache->pinfo = cached_plan_entry->pinfo;
+ }
else
{
- List *raw_parsetree_list;
+ MemoryContext alianable_context = fcontext;
+
+ /* We need to preserve parse info */
+ if (use_plan_cache)
+ {
+ alianable_context = AllocSetContextCreate(CurrentMemoryContext,
+ "SQL function plan entry alianable context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ MemoryContextSwitchTo(alianable_context);
+ }
- raw_parsetree_list = pg_parse_query(fcache->src);
+ /*
+ * 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);
+
+ if (use_plan_cache)
+ MemoryContextSwitchTo(fcontext);
- foreach(lc, raw_parsetree_list)
+ /*
+ * 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.
+ */
+
+ plansource_list = NIL;
+
+ queryTree_list = NIL;
+ if (!isNull)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
- CachedPlanSource *plansource;
-
- plansource = CreateCachedPlan(parsetree, fcache->src, CreateCommandTag(parsetree->stmt));
- plansource_list = lappend(plansource_list, plansource);
-
- 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);
+ Node *n;
+ List *stored_query_list;
+
+ 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;
+ CachedPlanSource *plansource;
+
+ AcquireRewriteLocks(parsetree, true, false);
+
+ plansource = CreateCachedPlanForQuery(parsetree, fcache->src, CreateCommandTag((Node *) parsetree));
+ plansource_list = lappend(plansource_list, plansource);
+
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
}
- }
+ else
+ {
+ List *raw_parsetree_list;
- /*
- * Check that there are no statements we don't want to allow.
- */
- check_sql_fn_statements(queryTree_list);
+ raw_parsetree_list = pg_parse_query(fcache->src);
- /*
- * Check that the function returns the type it claims to. Although in
- * simple cases this was already done when the function was defined, we
- * have to recheck because database objects used in the function's queries
- * might have changed type. We'd have to recheck anyway if the function
- * had any polymorphic arguments. Moreover, check_sql_fn_retval takes
- * care of injecting any required column type coercions. (But we don't
- * ask it to insert nulls for dropped columns; the junkfilter handles
- * that.)
- *
- * Note: we set fcache->returnsTuple according to whether we are returning
- * the whole tuple result or just a single column. In the latter case we
- * clear returnsTuple because we need not act different from the scalar
- * result case, even if it's a rowtype column. (However, we have to force
- * lazy eval mode in that case; otherwise we'd need extra code to expand
- * the rowtype column into multiple columns, since we have no way to
- * notify the caller that it should do that.)
- */
- fcache->returnsTuple = check_sql_fn_retval(queryTree_list,
- rettype,
- rettupdesc,
- procedureStruct->prokind,
- false,
- &resulttlist);
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+ CachedPlanSource *plansource;
+
+ plansource = CreateCachedPlan(parsetree, fcache->src, CreateCommandTag(parsetree->stmt));
+ plansource_list = lappend(plansource_list, plansource);
+
+ 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);
+ }
+ }
- /*
- * Queries could be rewritten by check_sql_fn_retval(). Now when they have
- * their final form, we can complete plan cache entry creation.
- */
- if (plansource_list != NIL)
- {
- ListCell *qlc;
- ListCell *plc;
+ /*
+ * Check that there are no statements we don't want to allow.
+ */
+ check_sql_fn_statements(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.)
+ *
+ * Note: we set fcache->returnsTuple according to whether we are
+ * returning the whole tuple result or just a single column. In the
+ * latter case we clear returnsTuple because we need not act different
+ * from the scalar result case, even if it's a rowtype column.
+ * (However, we have to force lazy eval mode in that case; otherwise
+ * we'd need extra code to expand the rowtype column into multiple
+ * columns, since we have no way to notify the caller that it should
+ * do that.)
+ */
- forboth(qlc, queryTree_list, plc, plansource_list)
+ fcache->returnsTuple = check_sql_fn_retval(queryTree_list,
+ rettype,
+ rettupdesc,
+ procedureStruct->prokind,
+ false,
+ &resulttlist);
+
+ /*
+ * Queries could be rewritten by check_sql_fn_retval(). Now when they
+ * have their final form, we can complete plan cache entry creation.
+ */
+ if (plansource_list != NIL)
{
- List *queryTree_sublist = lfirst(qlc);
- CachedPlanSource *plansource = lfirst(plc);
-
-
- /* Finish filling in the CachedPlanSource */
- CompleteCachedPlan(plansource,
- queryTree_sublist,
- NULL,
- NULL,
- 0,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- CURSOR_OPT_PARALLEL_OK | CURSOR_OPT_NO_SCROLL,
- false);
+ ListCell *qlc;
+ ListCell *plc;
+
+ forboth(qlc, queryTree_list, plc, plansource_list)
+ {
+ List *queryTree_sublist = lfirst(qlc);
+ CachedPlanSource *plansource = lfirst(plc);
+
+
+ /* Finish filling in the CachedPlanSource */
+ CompleteCachedPlan(plansource,
+ queryTree_sublist,
+ NULL,
+ NULL,
+ 0,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ CURSOR_OPT_PARALLEL_OK | CURSOR_OPT_NO_SCROLL,
+ false);
+ }
}
+
+ /* If we can possibly use cached plan entry, save it. */
+ if (use_plan_cache)
+ save_cached_plan_entry(&plan_cache_entry_key, procedureTuple, plansource_list, resulttlist, fcache->returnsTuple, fcache->pinfo, alianable_context);
}
/*
@@ -1120,9 +1511,6 @@ release_plans(List *cplans)
ReleaseCachedPlan(cplan, cplan->is_saved ? CurrentResourceOwner : NULL);
}
-
- /* Cleanup the list itself */
- list_free(cplans);
}
/*
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();
--
2.43.0