* namespaceUser is the userid the path has been computed for.
*
* Note: all data pointed to by these List variables is in TopMemoryContext.
+ *
+ * activePathGeneration is incremented whenever the effective values of
+ * activeSearchPath/activeCreationNamespace/activeTempCreationPending change.
+ * This can be used to quickly detect whether any change has happened since
+ * a previous examination of the search path state.
*/
/* These variables define the actually active state: */
/* if true, activeCreationNamespace is wrong, it should be temp namespace */
static bool activeTempCreationPending = false;
+/* current generation counter; make sure this is never zero */
+static uint64 activePathGeneration = 1;
+
/* These variables are the values last derived from namespace_search_path: */
static List *baseSearchPath = NIL;
schemas = list_delete_first(schemas);
}
result->schemas = schemas;
+ result->generation = activePathGeneration;
MemoryContextSwitchTo(oldcxt);
result->schemas = list_copy(path->schemas);
result->addCatalog = path->addCatalog;
result->addTemp = path->addTemp;
+ result->generation = path->generation;
return result;
}
/*
* OverrideSearchPathMatchesCurrent - does path match current setting?
+ *
+ * This is tested over and over in some common code paths, and in the typical
+ * scenario where the active search path seldom changes, it'll always succeed.
+ * We make that case fast by keeping a generation counter that is advanced
+ * whenever the active search path changes.
*/
bool
OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
recomputeNamespacePath();
+ /* Quick out if already known equal to active path. */
+ if (path->generation == activePathGeneration)
+ return true;
+
/* We scan down the activeSearchPath to see if it matches the input. */
lc = list_head(activeSearchPath);
}
if (lc)
return false;
+
+ /*
+ * Update path->generation so that future tests will return quickly, so
+ * long as the active search path doesn't change.
+ */
+ path->generation = activePathGeneration;
+
return true;
}
activeCreationNamespace = entry->creationNamespace;
activeTempCreationPending = false; /* XXX is this OK? */
+ /*
+ * We always increment activePathGeneration when pushing/popping an
+ * override path. In current usage, these actions always change the
+ * effective path state, so there's no value in checking to see if it
+ * didn't change.
+ */
+ activePathGeneration++;
+
MemoryContextSwitchTo(oldcxt);
}
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
}
+
+ /* As above, the generation always increments. */
+ activePathGeneration++;
}
ListCell *l;
bool temp_missing;
Oid firstNS;
+ bool pathChanged;
MemoryContext oldcxt;
/* Do nothing if an override search spec is active. */
oidlist = lcons_oid(myTempNamespace, oidlist);
/*
- * Now that we've successfully built the new list of namespace OIDs, save
- * it in permanent storage.
+ * We want to detect the case where the effective value of the base search
+ * path variables didn't change. As long as we're doing so, we can avoid
+ * copying the OID list unncessarily.
*/
- oldcxt = MemoryContextSwitchTo(TopMemoryContext);
- newpath = list_copy(oidlist);
- MemoryContextSwitchTo(oldcxt);
+ if (baseCreationNamespace == firstNS &&
+ baseTempCreationPending == temp_missing &&
+ equal(oidlist, baseSearchPath))
+ {
+ pathChanged = false;
+ }
+ else
+ {
+ pathChanged = true;
+
+ /* Must save OID list in permanent storage. */
+ oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+ newpath = list_copy(oidlist);
+ MemoryContextSwitchTo(oldcxt);
- /* Now safe to assign to state variables. */
- list_free(baseSearchPath);
- baseSearchPath = newpath;
- baseCreationNamespace = firstNS;
- baseTempCreationPending = temp_missing;
+ /* Now safe to assign to state variables. */
+ list_free(baseSearchPath);
+ baseSearchPath = newpath;
+ baseCreationNamespace = firstNS;
+ baseTempCreationPending = temp_missing;
+ }
/* Mark the path valid. */
baseSearchPathValid = true;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
+ /*
+ * Bump the generation only if something actually changed. (Notice that
+ * what we compared to was the old state of the base path variables; so
+ * this does not deal with the situation where we have just popped an
+ * override path and restored the prior state of the base path. Instead
+ * we rely on the override-popping logic to have bumped the generation.)
+ */
+ if (pathChanged)
+ activePathGeneration++;
+
/* Clean up. */
pfree(rawname);
list_free(namelist);
activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
+ /* Always bump generation --- see note in recomputeNamespacePath */
+ activePathGeneration++;
}
}
overrideStack = list_delete_first(overrideStack);
list_free(entry->searchPath);
pfree(entry);
+ /* Always bump generation --- see note in recomputeNamespacePath */
+ activePathGeneration++;
}
/* Activate the next level down. */
activeSearchPath = entry->searchPath;
activeCreationNamespace = entry->creationNamespace;
activeTempCreationPending = false; /* XXX is this OK? */
+
+ /*
+ * It's probably unnecessary to bump generation here, but this should
+ * not be a performance-critical case, so better to be over-cautious.
+ */
+ activePathGeneration++;
}
else
{
activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
+
+ /*
+ * If we popped an override stack entry, then we already bumped the
+ * generation above. If we did not, then the above assignments did
+ * nothing and we need not bump the generation.
+ */
}
}
activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
+ activePathGeneration++; /* pro forma */
}
else
{
}
}
+/*
+ * CachedPlanAllowsSimpleValidityCheck: can we use CachedPlanIsSimplyValid?
+ *
+ * This function, together with CachedPlanIsSimplyValid, provides a fast path
+ * for revalidating "simple" generic plans. The core requirement to be simple
+ * is that the plan must not require taking any locks, which translates to
+ * not touching any tables; this happens to match up well with an important
+ * use-case in PL/pgSQL. This function tests whether that's true, along
+ * with checking some other corner cases that we'd rather not bother with
+ * handling in the fast path. (Note that it's still possible for such a plan
+ * to be invalidated, for example due to a change in a function that was
+ * inlined into the plan.)
+ *
+ * This must only be called on known-valid generic plans (eg, ones just
+ * returned by GetCachedPlan). If it returns true, the caller may re-use
+ * the cached plan as long as CachedPlanIsSimplyValid returns true; that
+ * check is much cheaper than the full revalidation done by GetCachedPlan.
+ * Nonetheless, no required checks are omitted.
+ */
+bool
+CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
+ CachedPlan *plan)
+{
+ ListCell *lc;
+
+ /* Sanity-check that the caller gave us a validated generic plan. */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
+ Assert(plansource->is_valid);
+ Assert(plan->is_valid);
+ Assert(plan == plansource->gplan);
+
+ /* We don't support oneshot plans here. */
+ if (plansource->is_oneshot)
+ return false;
+ Assert(!plan->is_oneshot);
+
+ /*
+ * If the plan is dependent on RLS considerations, or it's transient,
+ * reject. These things probably can't ever happen for table-free
+ * queries, but for safety's sake let's check.
+ */
+ if (plansource->dependsOnRLS)
+ return false;
+ if (plan->dependsOnRole)
+ return false;
+ if (TransactionIdIsValid(plan->saved_xmin))
+ return false;
+
+ /*
+ * Reject if AcquirePlannerLocks would have anything to do. This is
+ * simplistic, but there's no need to inquire any more carefully; indeed,
+ * for current callers it shouldn't even be possible to hit any of these
+ * checks.
+ */
+ foreach(lc, plansource->query_list)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ if (query->commandType == CMD_UTILITY)
+ return false;
+ if (query->rtable || query->cteList || query->hasSubLinks)
+ return false;
+ }
+
+ /*
+ * Reject if AcquireExecutorLocks would have anything to do. This is
+ * probably unnecessary given the previous check, but let's be safe.
+ */
+ foreach(lc, plan->stmt_list)
+ {
+ PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc);
+ ListCell *lc2;
+
+ if (plannedstmt->commandType == CMD_UTILITY)
+ return false;
+
+ /*
+ * We have to grovel through the rtable because it's likely to contain
+ * an RTE_RESULT relation, rather than being totally empty.
+ */
+ foreach(lc2, plannedstmt->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
+
+ if (rte->rtekind == RTE_RELATION)
+ return false;
+ }
+ }
+
+ /*
+ * Okay, it's simple. Note that what we've primarily established here is
+ * that no locks need be taken before checking the plan's is_valid flag.
+ */
+ return true;
+}
+
+/*
+ * CachedPlanIsSimplyValid: quick check for plan still being valid
+ *
+ * This function must not be used unless CachedPlanAllowsSimpleValidityCheck
+ * previously said it was OK.
+ *
+ * If the plan is valid, and "owner" is not NULL, record a refcount on
+ * the plan in that resowner before returning. It is caller's responsibility
+ * to be sure that a refcount is held on any plan that's being actively used.
+ *
+ * The code here is unconditionally safe as long as the only use of this
+ * CachedPlanSource is in connection with the particular CachedPlan pointer
+ * that's passed in. If the plansource were being used for other purposes,
+ * it's possible that its generic plan could be invalidated and regenerated
+ * while the current caller wasn't looking, and then there could be a chance
+ * collision of address between this caller's now-stale plan pointer and the
+ * actual address of the new generic plan. For current uses, that scenario
+ * can't happen; but with a plansource shared across multiple uses, it'd be
+ * advisable to also save plan->generation and verify that that still matches.
+ */
+bool
+CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan,
+ ResourceOwner owner)
+{
+ /*
+ * Careful here: since the caller doesn't necessarily hold a refcount on
+ * the plan to start with, it's possible that "plan" is a dangling
+ * pointer. Don't dereference it until we've verified that it still
+ * matches the plansource's gplan (which is either valid or NULL).
+ */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+
+ /*
+ * Has cache invalidation fired on this plan? We can check this right
+ * away since there are no locks that we'd need to acquire first.
+ */
+ if (!plansource->is_valid || plan != plansource->gplan || !plan->is_valid)
+ return false;
+
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
+
+ /* Is the search_path still the same as when we made it? */
+ Assert(plansource->search_path != NULL);
+ if (!OverrideSearchPathMatchesCurrent(plansource->search_path))
+ return false;
+
+ /* It's still good. Bump refcount if requested. */
+ if (owner)
+ {
+ ResourceOwnerEnlargePlanCacheRefs(owner);
+ plan->refcount++;
+ ResourceOwnerRememberPlanCacheRef(owner, plan);
+ }
+
+ return true;
+}
+
/*
* CachedPlanSetParentContext: move a CachedPlanSource to a new memory context
*
CurrentResourceOwner = save;
}
+/*
+ * ResourceOwnerReleaseAllPlanCacheRefs
+ * Release the plancache references (only) held by this owner.
+ *
+ * We might eventually add similar functions for other resource types,
+ * but for now, only this is needed.
+ */
+void
+ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
+{
+ ResourceOwner save;
+ Datum foundres;
+
+ save = CurrentResourceOwner;
+ CurrentResourceOwner = owner;
+ while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
+ {
+ CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
+
+ ReleaseCachedPlan(res, true);
+ }
+ CurrentResourceOwner = save;
+}
+
/*
* ResourceOwnerDelete
* Delete an owner object and its descendants.
/*
* Structure for xxxOverrideSearchPath functions
+ *
+ * The generation counter is private to namespace.c and shouldn't be touched
+ * by other code. It can be initialized to zero if necessary (that means
+ * "not known equal to the current active path").
*/
typedef struct OverrideSearchPath
{
List *schemas; /* OIDs of explicitly named schemas */
bool addCatalog; /* implicitly prepend pg_catalog? */
bool addTemp; /* implicitly prepend temp schema? */
+ uint64 generation; /* for quick detection of equality to active */
} OverrideSearchPath;
/*
#include "nodes/params.h"
#include "tcop/cmdtag.h"
#include "utils/queryenvironment.h"
+#include "utils/resowner.h"
+
/* Forward declaration, to avoid including parsenodes.h here */
struct RawStmt;
QueryEnvironment *queryEnv);
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+extern bool CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
+ CachedPlan *plan);
+extern bool CachedPlanIsSimplyValid(CachedPlanSource *plansource,
+ CachedPlan *plan,
+ ResourceOwner owner);
+
extern CachedExpression *GetCachedExpression(Node *expr);
extern void FreeCachedExpression(CachedExpression *cexpr);
ResourceReleasePhase phase,
bool isCommit,
bool isTopLevel);
+extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner);
extern void ResourceOwnerDelete(ResourceOwner owner);
extern ResourceOwner ResourceOwnerGetParent(ResourceOwner owner);
extern void ResourceOwnerNewParent(ResourceOwner owner,
REGRESS_OPTS = --dbname=$(PL_TESTDB)
REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
- plpgsql_record plpgsql_cache plpgsql_transaction plpgsql_trap \
- plpgsql_trigger plpgsql_varprops
+ plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
+ plpgsql_trap plpgsql_trigger plpgsql_varprops
# where to find gen_keywordlist.pl and subsidiary files
TOOLSDIR = $(top_srcdir)/src/tools
--- /dev/null
+--
+-- Tests for plpgsql's handling of "simple" expressions
+--
+-- Check that changes to an inline-able function are handled correctly
+create function simplesql(int) returns int language sql
+as 'select $1';
+create function simplecaller() returns int language plpgsql
+as $$
+declare
+ sum int := 0;
+begin
+ for n in 1..10 loop
+ sum := sum + simplesql(n);
+ if n = 5 then
+ create or replace function simplesql(int) returns int language sql
+ as 'select $1 + 100';
+ end if;
+ end loop;
+ return sum;
+end$$;
+select simplecaller();
+ simplecaller
+--------------
+ 555
+(1 row)
+
+-- Check that changes in search path are dealt with correctly
+create schema simple1;
+create function simple1.simpletarget(int) returns int language plpgsql
+as $$begin return $1; end$$;
+create function simpletarget(int) returns int language plpgsql
+as $$begin return $1 + 100; end$$;
+create or replace function simplecaller() returns int language plpgsql
+as $$
+declare
+ sum int := 0;
+begin
+ for n in 1..10 loop
+ sum := sum + simpletarget(n);
+ if n = 5 then
+ set local search_path = 'simple1';
+ end if;
+ end loop;
+ return sum;
+end$$;
+select simplecaller();
+ simplecaller
+--------------
+ 555
+(1 row)
+
+-- try it with non-volatile functions, too
+alter function simple1.simpletarget(int) immutable;
+alter function simpletarget(int) immutable;
+select simplecaller();
+ simplecaller
+--------------
+ 555
+(1 row)
+
+-- make sure flushing local caches changes nothing
+\c -
+select simplecaller();
+ simplecaller
+--------------
+ 555
+(1 row)
+
static EState *shared_simple_eval_estate = NULL;
static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
+/*
+ * In addition to the shared simple-eval EState, we have a shared resource
+ * owner that holds refcounts on the CachedPlans for any "simple" expressions
+ * we have evaluated in the current transaction. This allows us to avoid
+ * continually grabbing and releasing a plan refcount when a simple expression
+ * is used over and over. (DO blocks use their own resowner, in exactly the
+ * same way described above for shared_simple_eval_estate.)
+ */
+static ResourceOwner shared_simple_eval_resowner = NULL;
+
/*
* Memory management within a plpgsql function generally works with three
* contexts:
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
ReturnSetInfo *rsi,
- EState *simple_eval_estate);
+ EState *simple_eval_estate,
+ ResourceOwner simple_eval_resowner);
static void exec_eval_cleanup(PLpgSQL_execstate *estate);
static void exec_prepare_plan(PLpgSQL_execstate *estate,
*
* This is also used to execute inline code blocks (DO blocks). The only
* difference that this code is aware of is that for a DO block, we want
- * to use a private simple_eval_estate, which is created and passed in by
- * the caller. For regular functions, pass NULL, which implies using
- * shared_simple_eval_estate. (When using a private simple_eval_estate,
+ * to use a private simple_eval_estate and a private simple_eval_resowner,
+ * which are created and passed in by the caller. For regular functions,
+ * pass NULL, which implies using shared_simple_eval_estate and
+ * shared_simple_eval_resowner. (When using a private simple_eval_estate,
* we must also use a private cast hashtable, but that's taken care of
* within plpgsql_estate_setup.)
* ----------
*/
Datum
plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
- EState *simple_eval_estate, bool atomic)
+ EState *simple_eval_estate,
+ ResourceOwner simple_eval_resowner,
+ bool atomic)
{
PLpgSQL_execstate estate;
ErrorContextCallback plerrcontext;
* Setup the execution state
*/
plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo,
- simple_eval_estate);
+ simple_eval_estate, simple_eval_resowner);
estate.atomic = atomic;
/*
/*
* Setup the execution state
*/
- plpgsql_estate_setup(&estate, func, NULL, NULL);
+ plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
estate.trigdata = trigdata;
/*
/*
* Setup the execution state
*/
- plpgsql_estate_setup(&estate, func, NULL, NULL);
+ plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
estate.evtrigdata = trigdata;
/*
* simple-expression infrastructure.
*/
estate->simple_eval_estate = NULL;
+ estate->simple_eval_resowner = NULL;
plpgsql_create_econtext(estate);
}
plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
ReturnSetInfo *rsi,
- EState *simple_eval_estate)
+ EState *simple_eval_estate,
+ ResourceOwner simple_eval_resowner)
{
HASHCTL ctl;
estate->cast_hash = shared_cast_hash;
estate->cast_hash_context = shared_cast_context;
}
+ /* likewise for the simple-expression resource owner */
+ if (simple_eval_resowner)
+ estate->simple_eval_resowner = simple_eval_resowner;
+ else
+ estate->simple_eval_resowner = shared_simple_eval_resowner;
/*
* We start with no stmt_mcontext; one will be created only if needed.
* data structures are gone.
*/
estate->simple_eval_estate = NULL;
+ estate->simple_eval_resowner = NULL;
plpgsql_create_econtext(estate);
return PLPGSQL_RC_OK;
* data structures are gone.
*/
estate->simple_eval_estate = NULL;
+ estate->simple_eval_resowner = NULL;
plpgsql_create_econtext(estate);
return PLPGSQL_RC_OK;
* someone might redefine a SQL function that had been inlined into the simple
* expression. That cannot cause a simple expression to become non-simple (or
* vice versa), but we do have to handle replacing the expression tree.
- * Fortunately it's normally inexpensive to call SPI_plan_get_cached_plan for
- * a simple expression.
*
* Note: if pass-by-reference, the result is in the eval_mcontext.
* It will be freed when exec_eval_cleanup is done.
{
ExprContext *econtext = estate->eval_econtext;
LocalTransactionId curlxid = MyProc->lxid;
- CachedPlan *cplan;
+ ParamListInfo paramLI;
void *save_setup_arg;
bool need_snapshot;
MemoryContext oldcontext;
/*
* If expression is in use in current xact, don't touch it.
*/
- if (expr->expr_simple_in_use && expr->expr_simple_lxid == curlxid)
+ if (unlikely(expr->expr_simple_in_use) &&
+ expr->expr_simple_lxid == curlxid)
return false;
/*
- * Revalidate cached plan, so that we will notice if it became stale. (We
- * need to hold a refcount while using the plan, anyway.) If replanning
- * is needed, do that work in the eval_mcontext.
+ * Check to see if the cached plan has been invalidated. If not, and this
+ * is the first use in the current transaction, save a plan refcount in
+ * the simple-expression resowner.
*/
- oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
- cplan = SPI_plan_get_cached_plan(expr->plan);
- MemoryContextSwitchTo(oldcontext);
+ if (likely(CachedPlanIsSimplyValid(expr->expr_simple_plansource,
+ expr->expr_simple_plan,
+ (expr->expr_simple_plan_lxid != curlxid ?
+ estate->simple_eval_resowner : NULL))))
+ {
+ /*
+ * It's still good, so just remember that we have a refcount on the
+ * plan in the current transaction. (If we already had one, this
+ * assignment is a no-op.)
+ */
+ expr->expr_simple_plan_lxid = curlxid;
+ }
+ else
+ {
+ /* Need to replan */
+ CachedPlan *cplan;
- /*
- * We can't get a failure here, because the number of CachedPlanSources in
- * the SPI plan can't change from what exec_simple_check_plan saw; it's a
- * property of the raw parsetree generated from the query text.
- */
- Assert(cplan != NULL);
+ /*
+ * If we have a valid refcount on some previous version of the plan,
+ * release it, so we don't leak plans intra-transaction.
+ */
+ if (expr->expr_simple_plan_lxid == curlxid)
+ {
+ ResourceOwner saveResourceOwner = CurrentResourceOwner;
- /* If it got replanned, update our copy of the simple expression */
- if (cplan->generation != expr->expr_simple_generation)
- {
+ CurrentResourceOwner = estate->simple_eval_resowner;
+ ReleaseCachedPlan(expr->expr_simple_plan, true);
+ CurrentResourceOwner = saveResourceOwner;
+ expr->expr_simple_plan = NULL;
+ expr->expr_simple_plan_lxid = InvalidLocalTransactionId;
+ }
+
+ /* Do the replanning work in the eval_mcontext */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ cplan = SPI_plan_get_cached_plan(expr->plan);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * We can't get a failure here, because the number of
+ * CachedPlanSources in the SPI plan can't change from what
+ * exec_simple_check_plan saw; it's a property of the raw parsetree
+ * generated from the query text.
+ */
+ Assert(cplan != NULL);
+
+ /*
+ * These tests probably can't fail either, but if they do, cope by
+ * declaring the plan to be non-simple. On success, we'll acquire a
+ * refcount on the new plan, stored in simple_eval_resowner.
+ */
+ if (CachedPlanAllowsSimpleValidityCheck(expr->expr_simple_plansource,
+ cplan) &&
+ CachedPlanIsSimplyValid(expr->expr_simple_plansource, cplan,
+ estate->simple_eval_resowner))
+ {
+ /* Remember that we have the refcount */
+ expr->expr_simple_plan = cplan;
+ expr->expr_simple_plan_lxid = curlxid;
+ }
+ else
+ {
+ /* Release SPI_plan_get_cached_plan's refcount */
+ ReleaseCachedPlan(cplan, true);
+ /* Mark expression as non-simple, and fail */
+ expr->expr_simple_expr = NULL;
+ return false;
+ }
+
+ /*
+ * SPI_plan_get_cached_plan acquired a plan refcount stored in the
+ * active resowner. We don't need that anymore, so release it.
+ */
+ ReleaseCachedPlan(cplan, true);
+
+ /* Extract desired scalar expression from cached plan */
exec_save_simple_expr(expr, cplan);
+
/* better recheck r/w safety, as it could change due to inlining */
if (expr->rwparam >= 0)
exec_check_rw_parameter(expr, expr->rwparam);
* Set up ParamListInfo to pass to executor. For safety, save and restore
* estate->paramLI->parserSetupArg around our use of the param list.
*/
- save_setup_arg = estate->paramLI->parserSetupArg;
+ paramLI = estate->paramLI;
+ save_setup_arg = paramLI->parserSetupArg;
- econtext->ecxt_param_list_info = setup_param_list(estate, expr);
+ /*
+ * We can skip using setup_param_list() in favor of just doing this
+ * unconditionally, because there's no need for the optimization of
+ * possibly setting ecxt_param_list_info to NULL; we've already forced use
+ * of a generic plan.
+ */
+ paramLI->parserSetupArg = (void *) expr;
+ econtext->ecxt_param_list_info = paramLI;
/*
* Prepare the expression for execution, if it's not been done already in
* the current transaction. (This will be forced to happen if we called
* exec_save_simple_expr above.)
*/
- if (expr->expr_simple_lxid != curlxid)
+ if (unlikely(expr->expr_simple_lxid != curlxid))
{
oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
expr->expr_simple_state =
econtext->ecxt_param_list_info = NULL;
- estate->paramLI->parserSetupArg = save_setup_arg;
+ paramLI->parserSetupArg = save_setup_arg;
if (need_snapshot)
PopActiveSnapshot();
MemoryContextSwitchTo(oldcontext);
- /*
- * Now we can release our refcount on the cached plan.
- */
- ReleaseCachedPlan(cplan, true);
-
/*
* That's it.
*/
/* Can't fail, because we checked for a single CachedPlanSource above */
Assert(cplan != NULL);
- /* Share the remaining work with replan code path */
- exec_save_simple_expr(expr, cplan);
+ /*
+ * Verify that plancache.c thinks the plan is simple enough to use
+ * CachedPlanIsSimplyValid. Given the restrictions above, it's unlikely
+ * that this could fail, but if it does, just treat plan as not simple.
+ */
+ if (CachedPlanAllowsSimpleValidityCheck(plansource, cplan))
+ {
+ /*
+ * OK, use CachedPlanIsSimplyValid to save a refcount on the plan in
+ * the simple-expression resowner. This shouldn't fail either, but if
+ * somehow it does, again we can cope by treating plan as not simple.
+ */
+ if (CachedPlanIsSimplyValid(plansource, cplan,
+ estate->simple_eval_resowner))
+ {
+ /* Remember that we have the refcount */
+ expr->expr_simple_plansource = plansource;
+ expr->expr_simple_plan = cplan;
+ expr->expr_simple_plan_lxid = MyProc->lxid;
+
+ /* Share the remaining work with the replan code path */
+ exec_save_simple_expr(expr, cplan);
+ }
+ }
- /* Release our plan refcount */
+ /*
+ * Release the plan refcount obtained by SPI_plan_get_cached_plan. (This
+ * refcount is held by the wrong resowner, so we can't just repurpose it.)
+ */
ReleaseCachedPlan(cplan, true);
}
* current transaction".
*/
expr->expr_simple_expr = tle_expr;
- expr->expr_simple_generation = cplan->generation;
expr->expr_simple_state = NULL;
expr->expr_simple_in_use = false;
expr->expr_simple_lxid = InvalidLocalTransactionId;
*
* We may need to create a new shared_simple_eval_estate too, if there's not
* one already for the current transaction. The EState will be cleaned up at
- * transaction end.
+ * transaction end. Ditto for shared_simple_eval_resowner.
*/
static void
plpgsql_create_econtext(PLpgSQL_execstate *estate)
estate->simple_eval_estate = shared_simple_eval_estate;
}
+ /*
+ * Likewise for the simple-expression resource owner.
+ */
+ if (estate->simple_eval_resowner == NULL)
+ {
+ if (shared_simple_eval_resowner == NULL)
+ shared_simple_eval_resowner =
+ ResourceOwnerCreate(TopTransactionResourceOwner,
+ "PL/pgSQL simple expressions");
+ estate->simple_eval_resowner = shared_simple_eval_resowner;
+ }
+
/*
* Create a child econtext for the current function.
*/
* plpgsql_xact_cb --- post-transaction-commit-or-abort cleanup
*
* If a simple-expression EState was created in the current transaction,
- * it has to be cleaned up.
+ * it has to be cleaned up. The same for the simple-expression resowner.
*/
void
plpgsql_xact_cb(XactEvent event, void *arg)
{
/*
- * If we are doing a clean transaction shutdown, free the EState (so that
- * any remaining resources will be released correctly). In an abort, we
- * expect the regular abort recovery procedures to release everything of
- * interest.
+ * If we are doing a clean transaction shutdown, free the EState and tell
+ * the resowner to release whatever plancache references it has, so that
+ * all remaining resources will be released correctly. (We don't need to
+ * actually delete the resowner here; deletion of the
+ * TopTransactionResourceOwner will take care of that.)
+ *
+ * In an abort, we expect the regular abort recovery procedures to release
+ * everything of interest, so just clear our pointers.
*/
if (event == XACT_EVENT_COMMIT ||
event == XACT_EVENT_PARALLEL_COMMIT ||
if (shared_simple_eval_estate)
FreeExecutorState(shared_simple_eval_estate);
shared_simple_eval_estate = NULL;
+ if (shared_simple_eval_resowner)
+ ResourceOwnerReleaseAllPlanCacheRefs(shared_simple_eval_resowner);
+ shared_simple_eval_resowner = NULL;
}
else if (event == XACT_EVENT_ABORT ||
event == XACT_EVENT_PARALLEL_ABORT)
{
simple_econtext_stack = NULL;
shared_simple_eval_estate = NULL;
+ shared_simple_eval_resowner = NULL;
}
}
retval = (Datum) 0;
}
else
- retval = plpgsql_exec_function(func, fcinfo, NULL, !nonatomic);
+ retval = plpgsql_exec_function(func, fcinfo,
+ NULL, NULL,
+ !nonatomic);
}
PG_FINALLY();
{
PLpgSQL_function *func;
FmgrInfo flinfo;
EState *simple_eval_estate;
+ ResourceOwner simple_eval_resowner;
Datum retval;
int rc;
flinfo.fn_mcxt = CurrentMemoryContext;
/*
- * Create a private EState for simple-expression execution. Notice that
- * this is NOT tied to transaction-level resources; it must survive any
- * COMMIT/ROLLBACK the DO block executes, since we will unconditionally
- * try to clean it up below. (Hence, be wary of adding anything that
- * could fail between here and the PG_TRY block.) See the comments for
- * shared_simple_eval_estate.
+ * Create a private EState and resowner for simple-expression execution.
+ * Notice that these are NOT tied to transaction-level resources; they
+ * must survive any COMMIT/ROLLBACK the DO block executes, since we will
+ * unconditionally try to clean them up below. (Hence, be wary of adding
+ * anything that could fail between here and the PG_TRY block.) See the
+ * comments for shared_simple_eval_estate.
*/
simple_eval_estate = CreateExecutorState();
+ simple_eval_resowner =
+ ResourceOwnerCreate(NULL, "PL/pgSQL DO block simple expressions");
/* And run the function */
PG_TRY();
{
- retval = plpgsql_exec_function(func, fake_fcinfo, simple_eval_estate, codeblock->atomic);
+ retval = plpgsql_exec_function(func, fake_fcinfo,
+ simple_eval_estate,
+ simple_eval_resowner,
+ codeblock->atomic);
}
PG_CATCH();
{
/*
* We need to clean up what would otherwise be long-lived resources
* accumulated by the failed DO block, principally cached plans for
- * statements (which can be flushed with plpgsql_free_function_memory)
- * and execution trees for simple expressions, which are in the
- * private EState.
+ * statements (which can be flushed by plpgsql_free_function_memory),
+ * execution trees for simple expressions, which are in the private
+ * EState, and cached-plan refcounts held by the private resowner.
*
* Before releasing the private EState, we must clean up any
* simple_econtext_stack entries pointing into it, which we can do by
GetCurrentSubTransactionId(),
0, NULL);
- /* Clean up the private EState */
+ /* Clean up the private EState and resowner */
FreeExecutorState(simple_eval_estate);
+ ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner);
+ ResourceOwnerDelete(simple_eval_resowner);
/* Function should now have no remaining use-counts ... */
func->use_count--;
}
PG_END_TRY();
- /* Clean up the private EState */
+ /* Clean up the private EState and resowner */
FreeExecutorState(simple_eval_estate);
+ ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner);
+ ResourceOwnerDelete(simple_eval_resowner);
/* Function should now have no remaining use-counts ... */
func->use_count--;
/* fields for "simple expression" fast-path execution: */
Expr *expr_simple_expr; /* NULL means not a simple expr */
- int expr_simple_generation; /* plancache generation we checked */
Oid expr_simple_type; /* result type Oid, if simple */
int32 expr_simple_typmod; /* result typmod, if simple */
bool expr_simple_mutable; /* true if simple expr is mutable */
+ /*
+ * If the expression was ever determined to be simple, we remember its
+ * CachedPlanSource and CachedPlan here. If expr_simple_plan_lxid matches
+ * current LXID, then we hold a refcount on expr_simple_plan in the
+ * current transaction. Otherwise we need to get one before re-using it.
+ */
+ CachedPlanSource *expr_simple_plansource; /* extracted from "plan" */
+ CachedPlan *expr_simple_plan; /* extracted from "plan" */
+ LocalTransactionId expr_simple_plan_lxid;
+
/*
* if expr is simple AND prepared in current transaction,
* expr_simple_state and expr_simple_in_use are valid. Test validity by
*/
ParamListInfo paramLI;
- /* EState to use for "simple" expression evaluation */
+ /* EState and resowner to use for "simple" expression evaluation */
EState *simple_eval_estate;
+ ResourceOwner simple_eval_resowner;
/* lookup table to use for executing type casts */
HTAB *cast_hash;
extern Datum plpgsql_exec_function(PLpgSQL_function *func,
FunctionCallInfo fcinfo,
EState *simple_eval_estate,
+ ResourceOwner simple_eval_resowner,
bool atomic);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
TriggerData *trigdata);
--- /dev/null
+--
+-- Tests for plpgsql's handling of "simple" expressions
+--
+
+-- Check that changes to an inline-able function are handled correctly
+create function simplesql(int) returns int language sql
+as 'select $1';
+
+create function simplecaller() returns int language plpgsql
+as $$
+declare
+ sum int := 0;
+begin
+ for n in 1..10 loop
+ sum := sum + simplesql(n);
+ if n = 5 then
+ create or replace function simplesql(int) returns int language sql
+ as 'select $1 + 100';
+ end if;
+ end loop;
+ return sum;
+end$$;
+
+select simplecaller();
+
+
+-- Check that changes in search path are dealt with correctly
+create schema simple1;
+
+create function simple1.simpletarget(int) returns int language plpgsql
+as $$begin return $1; end$$;
+
+create function simpletarget(int) returns int language plpgsql
+as $$begin return $1 + 100; end$$;
+
+create or replace function simplecaller() returns int language plpgsql
+as $$
+declare
+ sum int := 0;
+begin
+ for n in 1..10 loop
+ sum := sum + simpletarget(n);
+ if n = 5 then
+ set local search_path = 'simple1';
+ end if;
+ end loop;
+ return sum;
+end$$;
+
+select simplecaller();
+
+-- try it with non-volatile functions, too
+alter function simple1.simpletarget(int) immutable;
+alter function simpletarget(int) immutable;
+
+select simplecaller();
+
+-- make sure flushing local caches changes nothing
+\c -
+
+select simplecaller();