Commit 8f59f6b9 authored by Tom Lane's avatar Tom Lane

Improve performance of "simple expressions" in PL/pgSQL.

For relatively simple expressions (say, "x + 1" or "x > 0"), plpgsql's
management overhead exceeds the cost of evaluating the expression.
This patch substantially improves that situation, providing roughly
2X speedup for such trivial expressions.

First, add infrastructure in the plancache to allow fast re-validation
of cached plans that contain no table access, and hence need no locks.
Teach plpgsql to use this infrastructure for expressions that it's
already deemed "simple" (which in particular will never contain table
references).

The fast path still requires checking that search_path hasn't changed,
so provide a fast path for OverrideSearchPathMatchesCurrent by
counting changes that have occurred to the active search path in the
current session.  This is simplistic but seems enough for now, seeing
that PushOverrideSearchPath is not used in any performance-critical
cases.

Second, manage the refcounts on simple expressions' cached plans using
a transaction-lifespan resource owner, so that we only need to take
and release an expression's refcount once per transaction not once per
expression evaluation.  The management of this resource owner exactly
parallels the existing management of plpgsql's simple-expression EState.

Add some regression tests covering this area, in particular verifying
that expression caching doesn't break semantics for search_path changes.

Patch by me, but it owes something to previous work by Amit Langote,
who recognized that getting rid of plancache-related overhead would
be a useful thing to do here.  Also thanks to Andres Freund for review.

Discussion: https://postgr.es/m/CAFj8pRDRVfLdAxsWeVLzCAbkLFZhW549K+67tpOc-faC8uH8zw@mail.gmail.com
parent 86e5badd
...@@ -126,6 +126,11 @@ ...@@ -126,6 +126,11 @@
* namespaceUser is the userid the path has been computed for. * namespaceUser is the userid the path has been computed for.
* *
* Note: all data pointed to by these List variables is in TopMemoryContext. * 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: */ /* These variables define the actually active state: */
...@@ -138,6 +143,9 @@ static Oid activeCreationNamespace = InvalidOid; ...@@ -138,6 +143,9 @@ static Oid activeCreationNamespace = InvalidOid;
/* if true, activeCreationNamespace is wrong, it should be temp namespace */ /* if true, activeCreationNamespace is wrong, it should be temp namespace */
static bool activeTempCreationPending = false; 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: */ /* These variables are the values last derived from namespace_search_path: */
static List *baseSearchPath = NIL; static List *baseSearchPath = NIL;
...@@ -3373,6 +3381,7 @@ GetOverrideSearchPath(MemoryContext context) ...@@ -3373,6 +3381,7 @@ GetOverrideSearchPath(MemoryContext context)
schemas = list_delete_first(schemas); schemas = list_delete_first(schemas);
} }
result->schemas = schemas; result->schemas = schemas;
result->generation = activePathGeneration;
MemoryContextSwitchTo(oldcxt); MemoryContextSwitchTo(oldcxt);
...@@ -3393,12 +3402,18 @@ CopyOverrideSearchPath(OverrideSearchPath *path) ...@@ -3393,12 +3402,18 @@ CopyOverrideSearchPath(OverrideSearchPath *path)
result->schemas = list_copy(path->schemas); result->schemas = list_copy(path->schemas);
result->addCatalog = path->addCatalog; result->addCatalog = path->addCatalog;
result->addTemp = path->addTemp; result->addTemp = path->addTemp;
result->generation = path->generation;
return result; return result;
} }
/* /*
* OverrideSearchPathMatchesCurrent - does path match current setting? * 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 bool
OverrideSearchPathMatchesCurrent(OverrideSearchPath *path) OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
...@@ -3408,6 +3423,10 @@ OverrideSearchPathMatchesCurrent(OverrideSearchPath *path) ...@@ -3408,6 +3423,10 @@ OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
recomputeNamespacePath(); 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. */ /* We scan down the activeSearchPath to see if it matches the input. */
lc = list_head(activeSearchPath); lc = list_head(activeSearchPath);
...@@ -3440,6 +3459,13 @@ OverrideSearchPathMatchesCurrent(OverrideSearchPath *path) ...@@ -3440,6 +3459,13 @@ OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
} }
if (lc) if (lc)
return false; 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; return true;
} }
...@@ -3510,6 +3536,14 @@ PushOverrideSearchPath(OverrideSearchPath *newpath) ...@@ -3510,6 +3536,14 @@ PushOverrideSearchPath(OverrideSearchPath *newpath)
activeCreationNamespace = entry->creationNamespace; activeCreationNamespace = entry->creationNamespace;
activeTempCreationPending = false; /* XXX is this OK? */ 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); MemoryContextSwitchTo(oldcxt);
} }
...@@ -3551,6 +3585,9 @@ PopOverrideSearchPath(void) ...@@ -3551,6 +3585,9 @@ PopOverrideSearchPath(void)
activeCreationNamespace = baseCreationNamespace; activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending; activeTempCreationPending = baseTempCreationPending;
} }
/* As above, the generation always increments. */
activePathGeneration++;
} }
...@@ -3707,6 +3744,7 @@ recomputeNamespacePath(void) ...@@ -3707,6 +3744,7 @@ recomputeNamespacePath(void)
ListCell *l; ListCell *l;
bool temp_missing; bool temp_missing;
Oid firstNS; Oid firstNS;
bool pathChanged;
MemoryContext oldcxt; MemoryContext oldcxt;
/* Do nothing if an override search spec is active. */ /* Do nothing if an override search spec is active. */
...@@ -3814,9 +3852,21 @@ recomputeNamespacePath(void) ...@@ -3814,9 +3852,21 @@ recomputeNamespacePath(void)
oidlist = lcons_oid(myTempNamespace, oidlist); oidlist = lcons_oid(myTempNamespace, oidlist);
/* /*
* Now that we've successfully built the new list of namespace OIDs, save * We want to detect the case where the effective value of the base search
* it in permanent storage. * path variables didn't change. As long as we're doing so, we can avoid
* copying the OID list unncessarily.
*/ */
if (baseCreationNamespace == firstNS &&
baseTempCreationPending == temp_missing &&
equal(oidlist, baseSearchPath))
{
pathChanged = false;
}
else
{
pathChanged = true;
/* Must save OID list in permanent storage. */
oldcxt = MemoryContextSwitchTo(TopMemoryContext); oldcxt = MemoryContextSwitchTo(TopMemoryContext);
newpath = list_copy(oidlist); newpath = list_copy(oidlist);
MemoryContextSwitchTo(oldcxt); MemoryContextSwitchTo(oldcxt);
...@@ -3826,6 +3876,7 @@ recomputeNamespacePath(void) ...@@ -3826,6 +3876,7 @@ recomputeNamespacePath(void)
baseSearchPath = newpath; baseSearchPath = newpath;
baseCreationNamespace = firstNS; baseCreationNamespace = firstNS;
baseTempCreationPending = temp_missing; baseTempCreationPending = temp_missing;
}
/* Mark the path valid. */ /* Mark the path valid. */
baseSearchPathValid = true; baseSearchPathValid = true;
...@@ -3836,6 +3887,16 @@ recomputeNamespacePath(void) ...@@ -3836,6 +3887,16 @@ recomputeNamespacePath(void)
activeCreationNamespace = baseCreationNamespace; activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending; 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. */ /* Clean up. */
pfree(rawname); pfree(rawname);
list_free(namelist); list_free(namelist);
...@@ -4054,6 +4115,8 @@ AtEOXact_Namespace(bool isCommit, bool parallel) ...@@ -4054,6 +4115,8 @@ AtEOXact_Namespace(bool isCommit, bool parallel)
activeSearchPath = baseSearchPath; activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace; activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending; activeTempCreationPending = baseTempCreationPending;
/* Always bump generation --- see note in recomputeNamespacePath */
activePathGeneration++;
} }
} }
...@@ -4109,6 +4172,8 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, ...@@ -4109,6 +4172,8 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid,
overrideStack = list_delete_first(overrideStack); overrideStack = list_delete_first(overrideStack);
list_free(entry->searchPath); list_free(entry->searchPath);
pfree(entry); pfree(entry);
/* Always bump generation --- see note in recomputeNamespacePath */
activePathGeneration++;
} }
/* Activate the next level down. */ /* Activate the next level down. */
...@@ -4118,6 +4183,12 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, ...@@ -4118,6 +4183,12 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid,
activeSearchPath = entry->searchPath; activeSearchPath = entry->searchPath;
activeCreationNamespace = entry->creationNamespace; activeCreationNamespace = entry->creationNamespace;
activeTempCreationPending = false; /* XXX is this OK? */ 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 else
{ {
...@@ -4125,6 +4196,12 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, ...@@ -4125,6 +4196,12 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid,
activeSearchPath = baseSearchPath; activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace; activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending; 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.
*/
} }
} }
...@@ -4264,6 +4341,7 @@ InitializeSearchPath(void) ...@@ -4264,6 +4341,7 @@ InitializeSearchPath(void)
activeSearchPath = baseSearchPath; activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace; activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending; activeTempCreationPending = baseTempCreationPending;
activePathGeneration++; /* pro forma */
} }
else else
{ {
......
...@@ -1277,6 +1277,160 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) ...@@ -1277,6 +1277,160 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
} }
} }
/*
* 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 * CachedPlanSetParentContext: move a CachedPlanSource to a new memory context
* *
......
...@@ -678,6 +678,30 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, ...@@ -678,6 +678,30 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
CurrentResourceOwner = save; 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 * ResourceOwnerDelete
* Delete an owner object and its descendants. * Delete an owner object and its descendants.
......
...@@ -49,12 +49,17 @@ typedef enum TempNamespaceStatus ...@@ -49,12 +49,17 @@ typedef enum TempNamespaceStatus
/* /*
* Structure for xxxOverrideSearchPath functions * 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 typedef struct OverrideSearchPath
{ {
List *schemas; /* OIDs of explicitly named schemas */ List *schemas; /* OIDs of explicitly named schemas */
bool addCatalog; /* implicitly prepend pg_catalog? */ bool addCatalog; /* implicitly prepend pg_catalog? */
bool addTemp; /* implicitly prepend temp schema? */ bool addTemp; /* implicitly prepend temp schema? */
uint64 generation; /* for quick detection of equality to active */
} OverrideSearchPath; } OverrideSearchPath;
/* /*
......
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
#include "nodes/params.h" #include "nodes/params.h"
#include "tcop/cmdtag.h" #include "tcop/cmdtag.h"
#include "utils/queryenvironment.h" #include "utils/queryenvironment.h"
#include "utils/resowner.h"
/* Forward declaration, to avoid including parsenodes.h here */ /* Forward declaration, to avoid including parsenodes.h here */
struct RawStmt; struct RawStmt;
...@@ -220,6 +222,12 @@ extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource, ...@@ -220,6 +222,12 @@ extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
QueryEnvironment *queryEnv); QueryEnvironment *queryEnv);
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner); 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 CachedExpression *GetCachedExpression(Node *expr);
extern void FreeCachedExpression(CachedExpression *cexpr); extern void FreeCachedExpression(CachedExpression *cexpr);
......
...@@ -71,6 +71,7 @@ extern void ResourceOwnerRelease(ResourceOwner owner, ...@@ -71,6 +71,7 @@ extern void ResourceOwnerRelease(ResourceOwner owner,
ResourceReleasePhase phase, ResourceReleasePhase phase,
bool isCommit, bool isCommit,
bool isTopLevel); bool isTopLevel);
extern void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner);
extern void ResourceOwnerDelete(ResourceOwner owner); extern void ResourceOwnerDelete(ResourceOwner owner);
extern ResourceOwner ResourceOwnerGetParent(ResourceOwner owner); extern ResourceOwner ResourceOwnerGetParent(ResourceOwner owner);
extern void ResourceOwnerNewParent(ResourceOwner owner, extern void ResourceOwnerNewParent(ResourceOwner owner,
......
...@@ -33,8 +33,8 @@ DATA = plpgsql.control plpgsql--1.0.sql ...@@ -33,8 +33,8 @@ DATA = plpgsql.control plpgsql--1.0.sql
REGRESS_OPTS = --dbname=$(PL_TESTDB) REGRESS_OPTS = --dbname=$(PL_TESTDB)
REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \ REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
plpgsql_record plpgsql_cache plpgsql_transaction plpgsql_trap \ plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
plpgsql_trigger plpgsql_varprops plpgsql_trap plpgsql_trigger plpgsql_varprops
# where to find gen_keywordlist.pl and subsidiary files # where to find gen_keywordlist.pl and subsidiary files
TOOLSDIR = $(top_srcdir)/src/tools TOOLSDIR = $(top_srcdir)/src/tools
......
--
-- 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)
...@@ -102,6 +102,16 @@ typedef struct SimpleEcontextStackEntry ...@@ -102,6 +102,16 @@ typedef struct SimpleEcontextStackEntry
static EState *shared_simple_eval_estate = NULL; static EState *shared_simple_eval_estate = NULL;
static SimpleEcontextStackEntry *simple_econtext_stack = 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 * Memory management within a plpgsql function generally works with three
* contexts: * contexts:
...@@ -321,7 +331,8 @@ static int exec_stmt_set(PLpgSQL_execstate *estate, ...@@ -321,7 +331,8 @@ static int exec_stmt_set(PLpgSQL_execstate *estate,
static void plpgsql_estate_setup(PLpgSQL_execstate *estate, static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func, PLpgSQL_function *func,
ReturnSetInfo *rsi, 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_eval_cleanup(PLpgSQL_execstate *estate);
static void exec_prepare_plan(PLpgSQL_execstate *estate, static void exec_prepare_plan(PLpgSQL_execstate *estate,
...@@ -447,16 +458,19 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate, ...@@ -447,16 +458,19 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
* *
* This is also used to execute inline code blocks (DO blocks). The only * 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 * 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 * to use a private simple_eval_estate and a private simple_eval_resowner,
* the caller. For regular functions, pass NULL, which implies using * which are created and passed in by the caller. For regular functions,
* shared_simple_eval_estate. (When using a private simple_eval_estate, * 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 * we must also use a private cast hashtable, but that's taken care of
* within plpgsql_estate_setup.) * within plpgsql_estate_setup.)
* ---------- * ----------
*/ */
Datum Datum
plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo, 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; PLpgSQL_execstate estate;
ErrorContextCallback plerrcontext; ErrorContextCallback plerrcontext;
...@@ -467,7 +481,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo, ...@@ -467,7 +481,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
* Setup the execution state * Setup the execution state
*/ */
plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo, plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo,
simple_eval_estate); simple_eval_estate, simple_eval_resowner);
estate.atomic = atomic; estate.atomic = atomic;
/* /*
...@@ -904,7 +918,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func, ...@@ -904,7 +918,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
/* /*
* Setup the execution state * Setup the execution state
*/ */
plpgsql_estate_setup(&estate, func, NULL, NULL); plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
estate.trigdata = trigdata; estate.trigdata = trigdata;
/* /*
...@@ -1142,7 +1156,7 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata) ...@@ -1142,7 +1156,7 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
/* /*
* Setup the execution state * Setup the execution state
*/ */
plpgsql_estate_setup(&estate, func, NULL, NULL); plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
estate.evtrigdata = trigdata; estate.evtrigdata = trigdata;
/* /*
...@@ -2326,6 +2340,7 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) ...@@ -2326,6 +2340,7 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
* simple-expression infrastructure. * simple-expression infrastructure.
*/ */
estate->simple_eval_estate = NULL; estate->simple_eval_estate = NULL;
estate->simple_eval_resowner = NULL;
plpgsql_create_econtext(estate); plpgsql_create_econtext(estate);
} }
...@@ -3881,7 +3896,8 @@ static void ...@@ -3881,7 +3896,8 @@ static void
plpgsql_estate_setup(PLpgSQL_execstate *estate, plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func, PLpgSQL_function *func,
ReturnSetInfo *rsi, ReturnSetInfo *rsi,
EState *simple_eval_estate) EState *simple_eval_estate,
ResourceOwner simple_eval_resowner)
{ {
HASHCTL ctl; HASHCTL ctl;
...@@ -3972,6 +3988,11 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate, ...@@ -3972,6 +3988,11 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->cast_hash = shared_cast_hash; estate->cast_hash = shared_cast_hash;
estate->cast_hash_context = shared_cast_context; 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. * We start with no stmt_mcontext; one will be created only if needed.
...@@ -4836,6 +4857,7 @@ exec_stmt_commit(PLpgSQL_execstate *estate, PLpgSQL_stmt_commit *stmt) ...@@ -4836,6 +4857,7 @@ exec_stmt_commit(PLpgSQL_execstate *estate, PLpgSQL_stmt_commit *stmt)
* data structures are gone. * data structures are gone.
*/ */
estate->simple_eval_estate = NULL; estate->simple_eval_estate = NULL;
estate->simple_eval_resowner = NULL;
plpgsql_create_econtext(estate); plpgsql_create_econtext(estate);
return PLPGSQL_RC_OK; return PLPGSQL_RC_OK;
...@@ -4862,6 +4884,7 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt) ...@@ -4862,6 +4884,7 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
* data structures are gone. * data structures are gone.
*/ */
estate->simple_eval_estate = NULL; estate->simple_eval_estate = NULL;
estate->simple_eval_resowner = NULL;
plpgsql_create_econtext(estate); plpgsql_create_econtext(estate);
return PLPGSQL_RC_OK; return PLPGSQL_RC_OK;
...@@ -6074,8 +6097,6 @@ loop_exit: ...@@ -6074,8 +6097,6 @@ loop_exit:
* someone might redefine a SQL function that had been inlined into the simple * 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 * expression. That cannot cause a simple expression to become non-simple (or
* vice versa), but we do have to handle replacing the expression tree. * 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. * Note: if pass-by-reference, the result is in the eval_mcontext.
* It will be freed when exec_eval_cleanup is done. * It will be freed when exec_eval_cleanup is done.
...@@ -6091,7 +6112,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -6091,7 +6112,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
{ {
ExprContext *econtext = estate->eval_econtext; ExprContext *econtext = estate->eval_econtext;
LocalTransactionId curlxid = MyProc->lxid; LocalTransactionId curlxid = MyProc->lxid;
CachedPlan *cplan; ParamListInfo paramLI;
void *save_setup_arg; void *save_setup_arg;
bool need_snapshot; bool need_snapshot;
MemoryContext oldcontext; MemoryContext oldcontext;
...@@ -6105,29 +6126,92 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -6105,29 +6126,92 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/* /*
* If expression is in use in current xact, don't touch it. * 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; return false;
/* /*
* Revalidate cached plan, so that we will notice if it became stale. (We * Check to see if the cached plan has been invalidated. If not, and this
* need to hold a refcount while using the plan, anyway.) If replanning * is the first use in the current transaction, save a plan refcount in
* is needed, do that work in the eval_mcontext. * the simple-expression resowner.
*/
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;
/*
* 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;
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)); oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
cplan = SPI_plan_get_cached_plan(expr->plan); cplan = SPI_plan_get_cached_plan(expr->plan);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
/* /*
* We can't get a failure here, because the number of CachedPlanSources in * We can't get a failure here, because the number of
* the SPI plan can't change from what exec_simple_check_plan saw; it's a * CachedPlanSources in the SPI plan can't change from what
* property of the raw parsetree generated from the query text. * exec_simple_check_plan saw; it's a property of the raw parsetree
* generated from the query text.
*/ */
Assert(cplan != NULL); Assert(cplan != NULL);
/* If it got replanned, update our copy of the simple expression */ /*
if (cplan->generation != expr->expr_simple_generation) * 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); exec_save_simple_expr(expr, cplan);
/* better recheck r/w safety, as it could change due to inlining */ /* better recheck r/w safety, as it could change due to inlining */
if (expr->rwparam >= 0) if (expr->rwparam >= 0)
exec_check_rw_parameter(expr, expr->rwparam); exec_check_rw_parameter(expr, expr->rwparam);
...@@ -6143,16 +6227,24 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -6143,16 +6227,24 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
* Set up ParamListInfo to pass to executor. For safety, save and restore * Set up ParamListInfo to pass to executor. For safety, save and restore
* estate->paramLI->parserSetupArg around our use of the param list. * 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 * 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 * the current transaction. (This will be forced to happen if we called
* exec_save_simple_expr above.) * 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); oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
expr->expr_simple_state = expr->expr_simple_state =
...@@ -6200,18 +6292,13 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -6200,18 +6292,13 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
econtext->ecxt_param_list_info = NULL; econtext->ecxt_param_list_info = NULL;
estate->paramLI->parserSetupArg = save_setup_arg; paramLI->parserSetupArg = save_setup_arg;
if (need_snapshot) if (need_snapshot)
PopActiveSnapshot(); PopActiveSnapshot();
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
/*
* Now we can release our refcount on the cached plan.
*/
ReleaseCachedPlan(cplan, true);
/* /*
* That's it. * That's it.
*/ */
...@@ -7999,10 +8086,35 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) ...@@ -7999,10 +8086,35 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
/* Can't fail, because we checked for a single CachedPlanSource above */ /* Can't fail, because we checked for a single CachedPlanSource above */
Assert(cplan != NULL); Assert(cplan != NULL);
/* Share the remaining work with replan code path */ /*
* 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); 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); ReleaseCachedPlan(cplan, true);
} }
...@@ -8075,7 +8187,6 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan) ...@@ -8075,7 +8187,6 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan)
* current transaction". * current transaction".
*/ */
expr->expr_simple_expr = tle_expr; expr->expr_simple_expr = tle_expr;
expr->expr_simple_generation = cplan->generation;
expr->expr_simple_state = NULL; expr->expr_simple_state = NULL;
expr->expr_simple_in_use = false; expr->expr_simple_in_use = false;
expr->expr_simple_lxid = InvalidLocalTransactionId; expr->expr_simple_lxid = InvalidLocalTransactionId;
...@@ -8211,7 +8322,7 @@ exec_set_found(PLpgSQL_execstate *estate, bool state) ...@@ -8211,7 +8322,7 @@ exec_set_found(PLpgSQL_execstate *estate, bool state)
* *
* We may need to create a new shared_simple_eval_estate too, if there's not * 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 * 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 static void
plpgsql_create_econtext(PLpgSQL_execstate *estate) plpgsql_create_econtext(PLpgSQL_execstate *estate)
...@@ -8244,6 +8355,18 @@ plpgsql_create_econtext(PLpgSQL_execstate *estate) ...@@ -8244,6 +8355,18 @@ plpgsql_create_econtext(PLpgSQL_execstate *estate)
estate->simple_eval_estate = shared_simple_eval_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. * Create a child econtext for the current function.
*/ */
...@@ -8290,16 +8413,20 @@ plpgsql_destroy_econtext(PLpgSQL_execstate *estate) ...@@ -8290,16 +8413,20 @@ plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
* plpgsql_xact_cb --- post-transaction-commit-or-abort cleanup * plpgsql_xact_cb --- post-transaction-commit-or-abort cleanup
* *
* If a simple-expression EState was created in the current transaction, * 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 void
plpgsql_xact_cb(XactEvent event, void *arg) plpgsql_xact_cb(XactEvent event, void *arg)
{ {
/* /*
* If we are doing a clean transaction shutdown, free the EState (so that * If we are doing a clean transaction shutdown, free the EState and tell
* any remaining resources will be released correctly). In an abort, we * the resowner to release whatever plancache references it has, so that
* expect the regular abort recovery procedures to release everything of * all remaining resources will be released correctly. (We don't need to
* interest. * 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 || if (event == XACT_EVENT_COMMIT ||
event == XACT_EVENT_PARALLEL_COMMIT || event == XACT_EVENT_PARALLEL_COMMIT ||
...@@ -8310,12 +8437,16 @@ plpgsql_xact_cb(XactEvent event, void *arg) ...@@ -8310,12 +8437,16 @@ plpgsql_xact_cb(XactEvent event, void *arg)
if (shared_simple_eval_estate) if (shared_simple_eval_estate)
FreeExecutorState(shared_simple_eval_estate); FreeExecutorState(shared_simple_eval_estate);
shared_simple_eval_estate = NULL; 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 || else if (event == XACT_EVENT_ABORT ||
event == XACT_EVENT_PARALLEL_ABORT) event == XACT_EVENT_PARALLEL_ABORT)
{ {
simple_econtext_stack = NULL; simple_econtext_stack = NULL;
shared_simple_eval_estate = NULL; shared_simple_eval_estate = NULL;
shared_simple_eval_resowner = NULL;
} }
} }
......
...@@ -262,7 +262,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS) ...@@ -262,7 +262,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
retval = (Datum) 0; retval = (Datum) 0;
} }
else else
retval = plpgsql_exec_function(func, fcinfo, NULL, !nonatomic); retval = plpgsql_exec_function(func, fcinfo,
NULL, NULL,
!nonatomic);
} }
PG_FINALLY(); PG_FINALLY();
{ {
...@@ -297,6 +299,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) ...@@ -297,6 +299,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
PLpgSQL_function *func; PLpgSQL_function *func;
FmgrInfo flinfo; FmgrInfo flinfo;
EState *simple_eval_estate; EState *simple_eval_estate;
ResourceOwner simple_eval_resowner;
Datum retval; Datum retval;
int rc; int rc;
...@@ -324,28 +327,33 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) ...@@ -324,28 +327,33 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
flinfo.fn_mcxt = CurrentMemoryContext; flinfo.fn_mcxt = CurrentMemoryContext;
/* /*
* Create a private EState for simple-expression execution. Notice that * Create a private EState and resowner for simple-expression execution.
* this is NOT tied to transaction-level resources; it must survive any * Notice that these are NOT tied to transaction-level resources; they
* COMMIT/ROLLBACK the DO block executes, since we will unconditionally * must survive any COMMIT/ROLLBACK the DO block executes, since we will
* try to clean it up below. (Hence, be wary of adding anything that * unconditionally try to clean them up below. (Hence, be wary of adding
* could fail between here and the PG_TRY block.) See the comments for * anything that could fail between here and the PG_TRY block.) See the
* shared_simple_eval_estate. * comments for shared_simple_eval_estate.
*/ */
simple_eval_estate = CreateExecutorState(); simple_eval_estate = CreateExecutorState();
simple_eval_resowner =
ResourceOwnerCreate(NULL, "PL/pgSQL DO block simple expressions");
/* And run the function */ /* And run the function */
PG_TRY(); 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(); PG_CATCH();
{ {
/* /*
* We need to clean up what would otherwise be long-lived resources * We need to clean up what would otherwise be long-lived resources
* accumulated by the failed DO block, principally cached plans for * accumulated by the failed DO block, principally cached plans for
* statements (which can be flushed with plpgsql_free_function_memory) * statements (which can be flushed by plpgsql_free_function_memory),
* and execution trees for simple expressions, which are in the * execution trees for simple expressions, which are in the private
* private EState. * EState, and cached-plan refcounts held by the private resowner.
* *
* Before releasing the private EState, we must clean up any * Before releasing the private EState, we must clean up any
* simple_econtext_stack entries pointing into it, which we can do by * simple_econtext_stack entries pointing into it, which we can do by
...@@ -358,8 +366,10 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) ...@@ -358,8 +366,10 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
GetCurrentSubTransactionId(), GetCurrentSubTransactionId(),
0, NULL); 0, NULL);
/* Clean up the private EState */ /* Clean up the private EState and resowner */
FreeExecutorState(simple_eval_estate); FreeExecutorState(simple_eval_estate);
ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner);
ResourceOwnerDelete(simple_eval_resowner);
/* Function should now have no remaining use-counts ... */ /* Function should now have no remaining use-counts ... */
func->use_count--; func->use_count--;
...@@ -373,8 +383,10 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) ...@@ -373,8 +383,10 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
} }
PG_END_TRY(); PG_END_TRY();
/* Clean up the private EState */ /* Clean up the private EState and resowner */
FreeExecutorState(simple_eval_estate); FreeExecutorState(simple_eval_estate);
ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner);
ResourceOwnerDelete(simple_eval_resowner);
/* Function should now have no remaining use-counts ... */ /* Function should now have no remaining use-counts ... */
func->use_count--; func->use_count--;
......
...@@ -231,11 +231,20 @@ typedef struct PLpgSQL_expr ...@@ -231,11 +231,20 @@ typedef struct PLpgSQL_expr
/* fields for "simple expression" fast-path execution: */ /* fields for "simple expression" fast-path execution: */
Expr *expr_simple_expr; /* NULL means not a simple expr */ 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 */ Oid expr_simple_type; /* result type Oid, if simple */
int32 expr_simple_typmod; /* result typmod, if simple */ int32 expr_simple_typmod; /* result typmod, if simple */
bool expr_simple_mutable; /* true if simple expr is mutable */ 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, * if expr is simple AND prepared in current transaction,
* expr_simple_state and expr_simple_in_use are valid. Test validity by * expr_simple_state and expr_simple_in_use are valid. Test validity by
...@@ -1082,8 +1091,9 @@ typedef struct PLpgSQL_execstate ...@@ -1082,8 +1091,9 @@ typedef struct PLpgSQL_execstate
*/ */
ParamListInfo paramLI; ParamListInfo paramLI;
/* EState to use for "simple" expression evaluation */ /* EState and resowner to use for "simple" expression evaluation */
EState *simple_eval_estate; EState *simple_eval_estate;
ResourceOwner simple_eval_resowner;
/* lookup table to use for executing type casts */ /* lookup table to use for executing type casts */
HTAB *cast_hash; HTAB *cast_hash;
...@@ -1268,6 +1278,7 @@ extern void _PG_init(void); ...@@ -1268,6 +1278,7 @@ extern void _PG_init(void);
extern Datum plpgsql_exec_function(PLpgSQL_function *func, extern Datum plpgsql_exec_function(PLpgSQL_function *func,
FunctionCallInfo fcinfo, FunctionCallInfo fcinfo,
EState *simple_eval_estate, EState *simple_eval_estate,
ResourceOwner simple_eval_resowner,
bool atomic); bool atomic);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func, extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
TriggerData *trigdata); TriggerData *trigdata);
......
--
-- 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();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment