Improve performance of repeated CALLs within plpgsql procedures.

This patch essentially is cleaning up technical debt left behind
by the original implementation of plpgsql procedures, particularly
commit d92bc83c4.  That patch (or more precisely, follow-on patches
fixing its worst bugs) forced us to re-plan CALL and DO statements
each time through, if we're in a non-atomic context.  That wasn't
for any fundamental reason, but just because use of a saved plan
requires having a ResourceOwner to hold a reference count for the
plan, and we had no suitable resowner at hand, nor would the
available APIs support using one if we did.  While it's not that
expensive to create a "plan" for CALL/DO, the cycles do add up
in repeated executions.

This patch therefore makes the following API changes:

* GetCachedPlan/ReleaseCachedPlan are modified to let the caller
specify which resowner to use to pin the plan, rather than forcing
use of CurrentResourceOwner.

* spi.c gains a "SPI_execute_plan_extended" entry point that lets
callers say which resowner to use to pin the plan.  This borrows the
idea of an options struct from the recently added SPI_prepare_extended,
hopefully allowing future options to be added without more API breaks.
This supersedes SPI_execute_plan_with_paramlist (which I've marked
deprecated) as well as SPI_execute_plan_with_receiver (which is new
in v14, so I just took it out altogether).

* I also took the opportunity to remove the crude hack of letting
plpgsql reach into SPI private data structures to mark SPI plans as
"no_snapshot".  It's better to treat that as an option of
SPI_prepare_extended.

Now, when running a non-atomic procedure or DO block that contains
any CALL or DO commands, plpgsql creates a ResourceOwner that
will be used to pin the plans of the CALL/DO commands.  (In an
atomic context, we just use CurrentResourceOwner, as before.)
Having done this, we can just save CALL/DO plans normally,
whether or not they are used across transaction boundaries.
This seems to be good for something like 2X speedup of a CALL
of a trivial procedure with a few simple argument expressions.
By restricting the creation of an extra ResourceOwner like this,
there's essentially zero penalty in cases that can't benefit.

Pavel Stehule, with some further hacking by me

Discussion: https://postgr.es/m/CAFj8pRCLPdDAETvR7Po7gC5y_ibkn_-bOzbeJb39WHms01194Q@mail.gmail.com
This commit is contained in:
Tom Lane 2021-01-25 22:28:29 -05:00
parent 55ef8555f0
commit ee895a655c
15 changed files with 538 additions and 444 deletions

View File

@ -1722,6 +1722,172 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<!-- *********************************************** -->
<refentry id="spi-spi-execute-plan-extended">
<indexterm><primary>SPI_execute_plan_extended</primary></indexterm>
<refmeta>
<refentrytitle>SPI_execute_plan_extended</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_execute_plan_extended</refname>
<refpurpose>execute a statement prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
int SPI_execute_plan_extended(SPIPlanPtr <parameter>plan</parameter>,
const SPIExecuteOptions * <parameter>options</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_execute_plan_extended</function> executes a statement
prepared by <function>SPI_prepare</function> or one of its siblings.
This function is equivalent to <function>SPI_execute_plan</function>,
except that information about the parameter values to be passed to the
query is presented differently, and additional execution-controlling
options can be passed.
</para>
<para>
Query parameter values are represented by
a <literal>ParamListInfo</literal> struct, which is convenient for passing
down values that are already available in that format. Dynamic parameter
sets can also be used, via hook functions specified
in <literal>ParamListInfo</literal>.
</para>
<para>
Also, instead of always accumulating the result tuples into a
<varname>SPI_tuptable</varname> structure, tuples can be passed to a
caller-supplied <literal>DestReceiver</literal> object as they are
generated by the executor. This is particularly helpful for queries
that might generate many tuples, since the data can be processed
on-the-fly instead of being accumulated in memory.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>const SPIExecuteOptions * <parameter>options</parameter></literal></term>
<listitem>
<para>
struct containing optional arguments
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
Callers should always zero out the entire <parameter>options</parameter>
struct, then fill whichever fields they want to set. This ensures forward
compatibility of code, since any fields that are added to the struct in
future will be defined to behave backwards-compatibly if they are zero.
The currently available <parameter>options</parameter> fields are:
</para>
<variablelist>
<varlistentry>
<term><literal>ParamListInfo <parameter>params</parameter></literal></term>
<listitem>
<para>
data structure containing query parameter types and values; NULL if none
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<listitem>
<para><literal>true</literal> for read-only execution</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>no_snapshots</parameter></literal></term>
<listitem>
<para>
<literal>true</literal> prevents SPI from managing snapshots for
execution of the query; use with extreme caution
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>uint64 <parameter>tcount</parameter></literal></term>
<listitem>
<para>
maximum number of rows to return,
or <literal>0</literal> for no limit
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>DestReceiver * <parameter>dest</parameter></literal></term>
<listitem>
<para>
<literal>DestReceiver</literal> object that will receive any tuples
emitted by the query; if NULL, result tuples are accumulated into
a <varname>SPI_tuptable</varname> structure, as
in <function>SPI_execute_plan</function>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ResourceOwner <parameter>owner</parameter></literal></term>
<listitem>
<para>
The resource owner that will hold a reference count on the plan while
it is executed. If NULL, CurrentResourceOwner is used. Ignored for
non-saved plans, as SPI does not acquire reference counts on those.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
The return value is the same as for <function>SPI_execute_plan</function>.
</para>
<para>
When <parameter>dest</parameter> is NULL,
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> are set as in
<function>SPI_execute_plan</function>.
When <parameter>dest</parameter> is not NULL,
<varname>SPI_processed</varname> is set to zero and
<varname>SPI_tuptable</varname> is set to NULL. If a tuple count
is required, the caller's <literal>DestReceiver</literal> object must
calculate it.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-execute-plan-with-paramlist">
<indexterm><primary>SPI_execute_plan_with_paramlist</primary></indexterm>
@ -1757,6 +1923,11 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
already available in that format. It also supports use of dynamic
parameter sets via hook functions specified in <literal>ParamListInfo</literal>.
</para>
<para>
This function is now deprecated in favor
of <function>SPI_execute_plan_extended</function>.
</para>
</refsect1>
<refsect1>
@ -1817,120 +1988,6 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
<!-- *********************************************** -->
<refentry id="spi-spi-execute-plan-with-receiver">
<indexterm><primary>SPI_execute_plan_with_receiver</primary></indexterm>
<refmeta>
<refentrytitle>SPI_execute_plan_with_receiver</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_execute_plan_with_receiver</refname>
<refpurpose>execute a statement prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
int SPI_execute_plan_with_receiver(SPIPlanPtr <parameter>plan</parameter>,
ParamListInfo <parameter>params</parameter>,
bool <parameter>read_only</parameter>,
long <parameter>count</parameter>,
DestReceiver *<parameter>dest</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_execute_plan_with_receiver</function> executes a statement
prepared by <function>SPI_prepare</function>. This function is
equivalent to <function>SPI_execute_plan_with_paramlist</function>
except that, instead of always accumulating the result tuples into a
<varname>SPI_tuptable</varname> structure, tuples can be passed to a
caller-supplied <literal>DestReceiver</literal> object as they are
generated by the executor. This is particularly helpful for queries
that might generate many tuples, since the data can be processed
on-the-fly instead of being accumulated in memory.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ParamListInfo <parameter>params</parameter></literal></term>
<listitem>
<para>
data structure containing parameter types and values; NULL if none
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<listitem>
<para><literal>true</literal> for read-only execution</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>long <parameter>count</parameter></literal></term>
<listitem>
<para>
maximum number of rows to return,
or <literal>0</literal> for no limit
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>DestReceiver * <parameter>dest</parameter></literal></term>
<listitem>
<para>
<literal>DestReceiver</literal> object that will receive any tuples
emitted by the query; if NULL, this function is exactly equivalent to
<function>SPI_execute_plan_with_paramlist</function>
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
The return value is the same as for <function>SPI_execute_plan</function>.
</para>
<para>
When <parameter>dest</parameter> is NULL,
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> are set as in
<function>SPI_execute_plan</function>.
When <parameter>dest</parameter> is not NULL,
<varname>SPI_processed</varname> is set to zero and
<varname>SPI_tuptable</varname> is set to NULL. If a tuple count
is required, the caller's <literal>DestReceiver</literal> object must
calculate it.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-execp">
<indexterm><primary>SPI_execp</primary></indexterm>

View File

@ -230,7 +230,7 @@ ExecuteQuery(ParseState *pstate,
entry->plansource->query_string);
/* Replan if needed, and increment plan refcount for portal */
cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL);
cplan = GetCachedPlan(entry->plansource, paramLI, NULL, NULL);
plan_list = cplan->stmt_list;
/*
@ -651,7 +651,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
}
/* Replan if needed, and acquire a transient refcount */
cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv);
cplan = GetCachedPlan(entry->plansource, paramLI,
CurrentResourceOwner, queryEnv);
INSTR_TIME_SET_CURRENT(planduration);
INSTR_TIME_SUBTRACT(planduration, planstart);
@ -687,7 +688,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
if (estate)
FreeExecutorState(estate);
ReleaseCachedPlan(cplan, true);
ReleaseCachedPlan(cplan, CurrentResourceOwner);
}
/*

View File

@ -66,8 +66,10 @@ static void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan);
static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, bool fire_triggers, uint64 tcount,
DestReceiver *caller_dest);
bool read_only, bool no_snapshots,
bool fire_triggers, uint64 tcount,
DestReceiver *caller_dest,
ResourceOwner plan_owner);
static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes,
Datum *Values, const char *Nulls);
@ -521,7 +523,9 @@ SPI_execute(const char *src, bool read_only, long tcount)
res = _SPI_execute_plan(&plan, NULL,
InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount, NULL);
read_only, false,
true, tcount,
NULL, NULL);
_SPI_end_call(true);
return res;
@ -555,7 +559,9 @@ SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
_SPI_convert_params(plan->nargs, plan->argtypes,
Values, Nulls),
InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount, NULL);
read_only, false,
true, tcount,
NULL, NULL);
_SPI_end_call(true);
return res;
@ -568,6 +574,30 @@ SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount)
return SPI_execute_plan(plan, Values, Nulls, false, tcount);
}
/* Execute a previously prepared plan */
int
SPI_execute_plan_extended(SPIPlanPtr plan,
const SPIExecuteOptions *options)
{
int res;
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || options == NULL)
return SPI_ERROR_ARGUMENT;
res = _SPI_begin_call(true);
if (res < 0)
return res;
res = _SPI_execute_plan(plan, options->params,
InvalidSnapshot, InvalidSnapshot,
options->read_only, options->no_snapshots,
true, options->tcount,
options->dest, options->owner);
_SPI_end_call(true);
return res;
}
/* Execute a previously prepared plan */
int
SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params,
@ -584,36 +614,9 @@ SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params,
res = _SPI_execute_plan(plan, params,
InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount, NULL);
_SPI_end_call(true);
return res;
}
/*
* Execute a previously prepared plan. If dest isn't NULL, we send result
* tuples to the caller-supplied DestReceiver rather than through the usual
* SPI output arrangements. If dest is NULL this is equivalent to
* SPI_execute_plan_with_paramlist.
*/
int
SPI_execute_plan_with_receiver(SPIPlanPtr plan,
ParamListInfo params,
bool read_only, long tcount,
DestReceiver *dest)
{
int res;
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
return SPI_ERROR_ARGUMENT;
res = _SPI_begin_call(true);
if (res < 0)
return res;
res = _SPI_execute_plan(plan, params,
InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount, dest);
read_only, false,
true, tcount,
NULL, NULL);
_SPI_end_call(true);
return res;
@ -654,7 +657,9 @@ SPI_execute_snapshot(SPIPlanPtr plan,
_SPI_convert_params(plan->nargs, plan->argtypes,
Values, Nulls),
snapshot, crosscheck_snapshot,
read_only, fire_triggers, tcount, NULL);
read_only, false,
fire_triggers, tcount,
NULL, NULL);
_SPI_end_call(true);
return res;
@ -702,7 +707,9 @@ SPI_execute_with_args(const char *src,
res = _SPI_execute_plan(&plan, paramLI,
InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount, NULL);
read_only, false,
true, tcount,
NULL, NULL);
_SPI_end_call(true);
return res;
@ -746,7 +753,9 @@ SPI_execute_with_receiver(const char *src,
res = _SPI_execute_plan(&plan, params,
InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount, dest);
read_only, false,
true, tcount,
dest, NULL);
_SPI_end_call(true);
return res;
@ -1554,7 +1563,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
*/
/* Replan if needed, and increment plan refcount for portal */
cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv);
cplan = GetCachedPlan(plansource, paramLI, NULL, _SPI_current->queryEnv);
stmt_list = cplan->stmt_list;
if (!plan->saved)
@ -1568,7 +1577,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
oldcontext = MemoryContextSwitchTo(portal->portalContext);
stmt_list = copyObject(stmt_list);
MemoryContextSwitchTo(oldcontext);
ReleaseCachedPlan(cplan, false);
ReleaseCachedPlan(cplan, NULL);
cplan = NULL; /* portal shouldn't depend on cplan */
}
@ -1950,7 +1959,10 @@ SPI_plan_get_plan_sources(SPIPlanPtr plan)
/*
* SPI_plan_get_cached_plan --- get a SPI plan's generic CachedPlan,
* if the SPI plan contains exactly one CachedPlanSource. If not,
* return NULL. Caller is responsible for doing ReleaseCachedPlan().
* return NULL.
*
* The plan's refcount is incremented (and logged in CurrentResourceOwner,
* if it's a saved plan). Caller is responsible for doing ReleaseCachedPlan.
*
* This is exported so that PL/pgSQL can use it (this beats letting PL/pgSQL
* look directly into the SPIPlan for itself). It's not documented in
@ -1984,7 +1996,8 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
error_context_stack = &spierrcontext;
/* Get the generic plan for the query */
cplan = GetCachedPlan(plansource, NULL, plan->saved,
cplan = GetCachedPlan(plansource, NULL,
plan->saved ? CurrentResourceOwner : NULL,
_SPI_current->queryEnv);
Assert(cplan == plansource->gplan);
@ -2265,16 +2278,20 @@ _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan)
* behavior of taking a new snapshot for each query.
* crosscheck_snapshot: for RI use, all others pass InvalidSnapshot
* read_only: true for read-only execution (no CommandCounterIncrement)
* no_snapshots: true to skip snapshot management
* fire_triggers: true to fire AFTER triggers at end of query (normal case);
* false means any AFTER triggers are postponed to end of outer query
* tcount: execution tuple-count limit, or 0 for none
* caller_dest: DestReceiver to receive output, or NULL for normal SPI output
* plan_owner: ResourceOwner that will be used to hold refcount on plan;
* if NULL, CurrentResourceOwner is used (ignored for non-saved plan)
*/
static int
_SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, bool fire_triggers, uint64 tcount,
DestReceiver *caller_dest)
bool read_only, bool no_snapshots,
bool fire_triggers, uint64 tcount,
DestReceiver *caller_dest, ResourceOwner plan_owner)
{
int my_res = 0;
uint64 my_processed = 0;
@ -2315,10 +2332,10 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
* In the first two cases, we can just push the snap onto the stack once
* for the whole plan list.
*
* But if the plan has no_snapshots set to true, then don't manage
* snapshots at all. The caller should then take care of that.
* But if no_snapshots is true, then don't manage snapshots at all here.
* The caller must then take care of that.
*/
if (snapshot != InvalidSnapshot && !plan->no_snapshots)
if (snapshot != InvalidSnapshot && !no_snapshots)
{
if (read_only)
{
@ -2333,6 +2350,15 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
}
}
/*
* Ensure that we have a resource owner if plan is saved, and not if it
* isn't.
*/
if (!plan->saved)
plan_owner = NULL;
else if (plan_owner == NULL)
plan_owner = CurrentResourceOwner;
foreach(lc1, plan->plancache_list)
{
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
@ -2388,16 +2414,18 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
/*
* Replan if needed, and increment plan refcount. If it's a saved
* plan, the refcount must be backed by the CurrentResourceOwner.
* plan, the refcount must be backed by the plan_owner.
*/
cplan = GetCachedPlan(plansource, paramLI, plan->saved, _SPI_current->queryEnv);
cplan = GetCachedPlan(plansource, paramLI,
plan_owner, _SPI_current->queryEnv);
stmt_list = cplan->stmt_list;
/*
* In the default non-read-only case, get a new snapshot, replacing
* any that we pushed in a previous cycle.
*/
if (snapshot == InvalidSnapshot && !read_only && !plan->no_snapshots)
if (snapshot == InvalidSnapshot && !read_only && !no_snapshots)
{
if (pushed_active_snap)
PopActiveSnapshot();
@ -2450,7 +2478,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
* If not read-only mode, advance the command counter before each
* command and update the snapshot.
*/
if (!read_only && !plan->no_snapshots)
if (!read_only && !no_snapshots)
{
CommandCounterIncrement();
UpdateActiveSnapshotCommandId();
@ -2499,7 +2527,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
* caller must be in a nonatomic SPI context and manage
* snapshots itself.
*/
if (_SPI_current->atomic || !plan->no_snapshots)
if (_SPI_current->atomic || !no_snapshots)
context = PROCESS_UTILITY_QUERY;
else
context = PROCESS_UTILITY_QUERY_NONATOMIC;
@ -2586,7 +2614,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
}
/* Done with this plan, so release refcount */
ReleaseCachedPlan(cplan, plan->saved);
ReleaseCachedPlan(cplan, plan_owner);
cplan = NULL;
/*
@ -2606,7 +2634,7 @@ fail:
/* We no longer need the cached plan refcount, if any */
if (cplan)
ReleaseCachedPlan(cplan, plan->saved);
ReleaseCachedPlan(cplan, plan_owner);
/*
* Pop the error context stack

View File

@ -1963,7 +1963,7 @@ exec_bind_message(StringInfo input_message)
* will be generated in MessageContext. The plan refcount will be
* assigned to the Portal, so it will be released at portal destruction.
*/
cplan = GetCachedPlan(psrc, params, false, NULL);
cplan = GetCachedPlan(psrc, params, NULL, NULL);
/*
* Now we can define the portal.

View File

@ -533,7 +533,7 @@ ReleaseGenericPlan(CachedPlanSource *plansource)
Assert(plan->magic == CACHEDPLAN_MAGIC);
plansource->gplan = NULL;
ReleaseCachedPlan(plan, false);
ReleaseCachedPlan(plan, NULL);
}
}
@ -1130,16 +1130,16 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
* execution.
*
* On return, the refcount of the plan has been incremented; a later
* ReleaseCachedPlan() call is expected. The refcount has been reported
* to the CurrentResourceOwner if useResOwner is true (note that that must
* only be true if it's a "saved" CachedPlanSource).
* ReleaseCachedPlan() call is expected. If "owner" is not NULL then
* the refcount has been reported to that ResourceOwner (note that this
* is only supported for "saved" CachedPlanSources).
*
* Note: if any replanning activity is required, the caller's memory context
* is used for that work.
*/
CachedPlan *
GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
bool useResOwner, QueryEnvironment *queryEnv)
ResourceOwner owner, QueryEnvironment *queryEnv)
{
CachedPlan *plan = NULL;
List *qlist;
@ -1149,7 +1149,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
/* This seems worth a real test, though */
if (useResOwner && !plansource->is_saved)
if (owner && !plansource->is_saved)
elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan");
/* Make sure the querytree list is valid and we have parse-time locks */
@ -1228,11 +1228,11 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
Assert(plan != NULL);
/* Flag the plan as in use by caller */
if (useResOwner)
ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
if (owner)
ResourceOwnerEnlargePlanCacheRefs(owner);
plan->refcount++;
if (useResOwner)
ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
if (owner)
ResourceOwnerRememberPlanCacheRef(owner, plan);
/*
* Saved plans should be under CacheMemoryContext so they will not go away
@ -1253,21 +1253,21 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
* ReleaseCachedPlan: release active use of a cached plan.
*
* This decrements the reference count, and frees the plan if the count
* has thereby gone to zero. If useResOwner is true, it is assumed that
* the reference count is managed by the CurrentResourceOwner.
* has thereby gone to zero. If "owner" is not NULL, it is assumed that
* the reference count is managed by that ResourceOwner.
*
* Note: useResOwner = false is used for releasing references that are in
* Note: owner == NULL is used for releasing references that are in
* persistent data structures, such as the parent CachedPlanSource or a
* Portal. Transient references should be protected by a resource owner.
*/
void
ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
ReleaseCachedPlan(CachedPlan *plan, ResourceOwner owner)
{
Assert(plan->magic == CACHEDPLAN_MAGIC);
if (useResOwner)
if (owner)
{
Assert(plan->is_saved);
ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan);
ResourceOwnerForgetPlanCacheRef(owner, plan);
}
Assert(plan->refcount > 0);
plan->refcount--;

View File

@ -310,7 +310,7 @@ PortalReleaseCachedPlan(Portal portal)
{
if (portal->cplan)
{
ReleaseCachedPlan(portal->cplan, false);
ReleaseCachedPlan(portal->cplan, NULL);
portal->cplan = NULL;
/*

View File

@ -652,7 +652,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
if (isCommit)
PrintPlanCacheLeakWarning(res);
ReleaseCachedPlan(res, true);
ReleaseCachedPlan(res, owner);
}
/* Ditto for tupdesc references */
@ -703,18 +703,14 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
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);
ReleaseCachedPlan(res, owner);
}
CurrentResourceOwner = save;
}
/*

View File

@ -42,6 +42,17 @@ typedef struct SPIPrepareOptions
int cursorOptions;
} SPIPrepareOptions;
/* Optional arguments for SPI_execute_plan_extended */
typedef struct SPIExecuteOptions
{
ParamListInfo params;
bool read_only;
bool no_snapshots;
uint64 tcount;
DestReceiver *dest;
ResourceOwner owner;
} SPIExecuteOptions;
/* Plans are opaque structs for standard users of SPI */
typedef struct _SPI_plan *SPIPlanPtr;
@ -96,13 +107,11 @@ extern int SPI_finish(void);
extern int SPI_execute(const char *src, bool read_only, long tcount);
extern int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
bool read_only, long tcount);
extern int SPI_execute_plan_extended(SPIPlanPtr plan,
const SPIExecuteOptions *options);
extern int SPI_execute_plan_with_paramlist(SPIPlanPtr plan,
ParamListInfo params,
bool read_only, long tcount);
extern int SPI_execute_plan_with_receiver(SPIPlanPtr plan,
ParamListInfo params,
bool read_only, long tcount,
DestReceiver *dest);
extern int SPI_exec(const char *src, long tcount);
extern int SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls,
long tcount);

View File

@ -92,7 +92,6 @@ typedef struct _SPI_plan
int magic; /* should equal _SPI_PLAN_MAGIC */
bool saved; /* saved or unsaved plan? */
bool oneshot; /* one-shot plan? */
bool no_snapshots; /* let the caller handle the snapshots */
List *plancache_list; /* one CachedPlanSource per parsetree */
MemoryContext plancxt; /* Context containing _SPI_plan and data */
RawParseMode parse_mode; /* raw_parser() mode */

View File

@ -219,9 +219,9 @@ extern List *CachedPlanGetTargetList(CachedPlanSource *plansource,
extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
ParamListInfo boundParams,
bool useResOwner,
ResourceOwner owner,
QueryEnvironment *queryEnv);
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
extern void ReleaseCachedPlan(CachedPlan *plan, ResourceOwner owner);
extern bool CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
CachedPlan *plan,

View File

@ -369,6 +369,7 @@ do_compile(FunctionCallInfo fcinfo,
function->fn_prokind = procStruct->prokind;
function->nstatements = 0;
function->requires_procedure_resowner = false;
/*
* Initialize the compiler, particularly the namespace stack. The
@ -903,6 +904,7 @@ plpgsql_compile_inline(char *proc_source)
function->extra_errors = 0;
function->nstatements = 0;
function->requires_procedure_resowner = false;
plpgsql_ns_init();
plpgsql_ns_push(func_name, PLPGSQL_LABEL_BLOCK);

View File

@ -26,7 +26,6 @@
#include "commands/defrem.h"
#include "executor/execExpr.h"
#include "executor/spi.h"
#include "executor/spi_priv.h"
#include "executor/tstoreReceiver.h"
#include "funcapi.h"
#include "mb/stringinfo_mb.h"
@ -329,8 +328,7 @@ static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
static void exec_eval_cleanup(PLpgSQL_execstate *estate);
static void exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions,
bool keepplan);
PLpgSQL_expr *expr, int cursorOptions);
static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
static void exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan);
static void exec_check_rw_parameter(PLpgSQL_expr *expr);
@ -446,6 +444,8 @@ static char *format_expr_params(PLpgSQL_execstate *estate,
const PLpgSQL_expr *expr);
static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
ParamListInfo paramLI);
static PLpgSQL_variable *make_callstmt_target(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr);
/* ----------
@ -460,12 +460,18 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
* 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.)
* procedure_resowner is a resowner that will survive for the duration
* of execution of this function/procedure. It is needed only if we
* are doing non-atomic execution and there are CALL or DO statements
* in the function; otherwise it can be NULL. We use it to hold refcounts
* on the CALL/DO statements' plans.
* ----------
*/
Datum
plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
EState *simple_eval_estate,
ResourceOwner simple_eval_resowner,
ResourceOwner procedure_resowner,
bool atomic)
{
PLpgSQL_execstate estate;
@ -478,6 +484,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
*/
plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo,
simple_eval_estate, simple_eval_resowner);
estate.procedure_resowner = procedure_resowner;
estate.atomic = atomic;
/*
@ -2150,233 +2157,65 @@ static int
exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
{
PLpgSQL_expr *expr = stmt->expr;
SPIPlanPtr orig_plan = expr->plan;
bool local_plan;
PLpgSQL_variable *volatile cur_target = stmt->target;
volatile LocalTransactionId before_lxid;
LocalTransactionId before_lxid;
LocalTransactionId after_lxid;
volatile bool pushed_active_snap = false;
volatile int rc;
bool pushed_active_snap = false;
ParamListInfo paramLI;
SPIExecuteOptions options;
int rc;
/*
* If not in atomic context, we make a local plan that we'll just use for
* this invocation, and will free at the end. Otherwise, transaction ends
* would cause errors about plancache leaks.
*
* XXX This would be fixable with some plancache/resowner surgery
* elsewhere, but for now we'll just work around this here.
* Make a plan if we don't have one already.
*/
local_plan = !estate->atomic;
/* PG_TRY to ensure we clear the plan link, if needed, on failure */
PG_TRY();
if (expr->plan == NULL)
{
SPIPlanPtr plan = expr->plan;
ParamListInfo paramLI;
exec_prepare_plan(estate, expr, 0);
/*
* Make a plan if we don't have one, or if we need a local one. Note
* that we'll overwrite expr->plan either way; the PG_TRY block will
* ensure we undo that on the way out, if the plan is local.
* A CALL or DO can never be a simple expression.
*/
if (plan == NULL || local_plan)
{
/* Don't let SPI save the plan if it's going to be local */
exec_prepare_plan(estate, expr, 0, !local_plan);
plan = expr->plan;
/*
* A CALL or DO can never be a simple expression. (If it could
* be, we'd have to worry about saving/restoring the previous
* values of the related expr fields, not just expr->plan.)
*/
Assert(!expr->expr_simple_expr);
/*
* The procedure call could end transactions, which would upset
* the snapshot management in SPI_execute*, so don't let it do it.
* Instead, we set the snapshots ourselves below.
*/
plan->no_snapshots = true;
/*
* Force target to be recalculated whenever the plan changes, in
* case the procedure's argument list has changed.
*/
stmt->target = NULL;
cur_target = NULL;
}
Assert(!expr->expr_simple_expr);
/*
* We construct a DTYPE_ROW datum representing the plpgsql variables
* Also construct a DTYPE_ROW datum representing the plpgsql variables
* associated with the procedure's output arguments. Then we can use
* exec_move_row() to do the assignments.
*
* If we're using a local plan, also make a local target; otherwise,
* since the above code will force a new plan each time through, we'd
* repeatedly leak the memory for the target. (Note: we also leak the
* target when a plan change is forced, but that isn't so likely to
* cause excessive memory leaks.)
*/
if (stmt->is_call && cur_target == NULL)
{
Node *node;
FuncExpr *funcexpr;
HeapTuple func_tuple;
List *funcargs;
Oid *argtypes;
char **argnames;
char *argmodes;
MemoryContext oldcontext;
PLpgSQL_row *row;
int nfields;
int i;
ListCell *lc;
/* Use stmt_mcontext for any cruft accumulated here */
oldcontext = MemoryContextSwitchTo(get_stmt_mcontext(estate));
/*
* Get the parsed CallStmt, and look up the called procedure
*/
node = linitial_node(Query,
((CachedPlanSource *) linitial(plan->plancache_list))->query_list)->utilityStmt;
if (node == NULL || !IsA(node, CallStmt))
elog(ERROR, "query for CALL statement is not a CallStmt");
funcexpr = ((CallStmt *) node)->funcexpr;
func_tuple = SearchSysCache1(PROCOID,
ObjectIdGetDatum(funcexpr->funcid));
if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u",
funcexpr->funcid);
/*
* Extract function arguments, and expand any named-arg notation
*/
funcargs = expand_function_arguments(funcexpr->args,
funcexpr->funcresulttype,
func_tuple);
/*
* Get the argument names and modes, too
*/
get_func_arg_info(func_tuple, &argtypes, &argnames, &argmodes);
ReleaseSysCache(func_tuple);
/*
* Begin constructing row Datum; keep it in fn_cxt if it's to be
* long-lived.
*/
if (!local_plan)
MemoryContextSwitchTo(estate->func->fn_cxt);
row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = "(unnamed row)";
row->lineno = -1;
row->varnos = (int *) palloc(sizeof(int) * list_length(funcargs));
if (!local_plan)
MemoryContextSwitchTo(get_stmt_mcontext(estate));
/*
* Examine procedure's argument list. Each output arg position
* should be an unadorned plpgsql variable (Datum), which we can
* insert into the row Datum.
*/
nfields = 0;
i = 0;
foreach(lc, funcargs)
{
Node *n = lfirst(lc);
if (argmodes &&
(argmodes[i] == PROARGMODE_INOUT ||
argmodes[i] == PROARGMODE_OUT))
{
if (IsA(n, Param))
{
Param *param = (Param *) n;
/* paramid is offset by 1 (see make_datum_param()) */
row->varnos[nfields++] = param->paramid - 1;
}
else
{
/* report error using parameter name, if available */
if (argnames && argnames[i] && argnames[i][0])
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("procedure parameter \"%s\" is an output parameter but corresponding argument is not writable",
argnames[i])));
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("procedure parameter %d is an output parameter but corresponding argument is not writable",
i + 1)));
}
}
i++;
}
row->nfields = nfields;
cur_target = (PLpgSQL_variable *) row;
/* We can save and re-use the target datum, if it's not local */
if (!local_plan)
stmt->target = cur_target;
MemoryContextSwitchTo(oldcontext);
}
paramLI = setup_param_list(estate, expr);
before_lxid = MyProc->lxid;
/*
* Set snapshot only for non-read-only procedures, similar to SPI
* behavior.
*/
if (!estate->readonly_func)
{
PushActiveSnapshot(GetTransactionSnapshot());
pushed_active_snap = true;
}
rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
estate->readonly_func, 0);
if (stmt->is_call)
stmt->target = make_callstmt_target(estate, expr);
}
PG_CATCH();
{
/*
* If we are using a local plan, restore the old plan link.
*/
if (local_plan)
expr->plan = orig_plan;
PG_RE_THROW();
}
PG_END_TRY();
paramLI = setup_param_list(estate, expr);
before_lxid = MyProc->lxid;
/*
* If we are using a local plan, restore the old plan link; then free the
* local plan to avoid memory leaks. (Note that the error exit path above
* just clears the link without risking calling SPI_freeplan; we expect
* that xact cleanup will take care of the mess in that case.)
* The procedure call could end transactions, which would upset the
* snapshot management in SPI_execute*, so handle snapshots here instead.
* Set a snapshot only for non-read-only procedures, similar to SPI
* behavior.
*/
if (local_plan)
if (!estate->readonly_func)
{
SPIPlanPtr plan = expr->plan;
expr->plan = orig_plan;
SPI_freeplan(plan);
PushActiveSnapshot(GetTransactionSnapshot());
pushed_active_snap = true;
}
/*
* If we have a procedure-lifespan resowner, use that to hold the refcount
* for the plan. This avoids refcount leakage complaints if the called
* procedure ends the current transaction.
*/
memset(&options, 0, sizeof(options));
options.params = paramLI;
options.read_only = estate->readonly_func;
options.no_snapshots = true; /* disable SPI's snapshot management */
options.owner = estate->procedure_resowner;
rc = SPI_execute_plan_extended(expr->plan, &options);
if (rc < 0)
elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
elog(ERROR, "SPI_execute_plan_extended failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
after_lxid = MyProc->lxid;
@ -2410,10 +2249,10 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
{
SPITupleTable *tuptab = SPI_tuptable;
if (!cur_target)
if (!stmt->is_call)
elog(ERROR, "DO statement returned a row");
exec_move_row(estate, cur_target, tuptab->vals[0], tuptab->tupdesc);
exec_move_row(estate, stmt->target, tuptab->vals[0], tuptab->tupdesc);
}
else if (SPI_processed > 1)
elog(ERROR, "procedure call returned more than one row");
@ -2424,6 +2263,128 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
return PLPGSQL_RC_OK;
}
/*
* We construct a DTYPE_ROW datum representing the plpgsql variables
* associated with the procedure's output arguments. Then we can use
* exec_move_row() to do the assignments.
*/
static PLpgSQL_variable *
make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
{
List *plansources;
CachedPlanSource *plansource;
Node *node;
FuncExpr *funcexpr;
HeapTuple func_tuple;
List *funcargs;
Oid *argtypes;
char **argnames;
char *argmodes;
MemoryContext oldcontext;
PLpgSQL_row *row;
int nfields;
int i;
ListCell *lc;
/* Use eval_mcontext for any cruft accumulated here */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
/*
* Get the parsed CallStmt, and look up the called procedure
*/
plansources = SPI_plan_get_plan_sources(expr->plan);
if (list_length(plansources) != 1)
elog(ERROR, "query for CALL statement is not a CallStmt");
plansource = (CachedPlanSource *) linitial(plansources);
if (list_length(plansource->query_list) != 1)
elog(ERROR, "query for CALL statement is not a CallStmt");
node = linitial_node(Query, plansource->query_list)->utilityStmt;
if (node == NULL || !IsA(node, CallStmt))
elog(ERROR, "query for CALL statement is not a CallStmt");
funcexpr = ((CallStmt *) node)->funcexpr;
func_tuple = SearchSysCache1(PROCOID,
ObjectIdGetDatum(funcexpr->funcid));
if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u",
funcexpr->funcid);
/*
* Extract function arguments, and expand any named-arg notation
*/
funcargs = expand_function_arguments(funcexpr->args,
funcexpr->funcresulttype,
func_tuple);
/*
* Get the argument names and modes, too
*/
get_func_arg_info(func_tuple, &argtypes, &argnames, &argmodes);
ReleaseSysCache(func_tuple);
/*
* Begin constructing row Datum; keep it in fn_cxt so it's adequately
* long-lived.
*/
MemoryContextSwitchTo(estate->func->fn_cxt);
row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = "(unnamed row)";
row->lineno = -1;
row->varnos = (int *) palloc(sizeof(int) * list_length(funcargs));
MemoryContextSwitchTo(get_eval_mcontext(estate));
/*
* Examine procedure's argument list. Each output arg position should be
* an unadorned plpgsql variable (Datum), which we can insert into the row
* Datum.
*/
nfields = 0;
i = 0;
foreach(lc, funcargs)
{
Node *n = lfirst(lc);
if (argmodes &&
(argmodes[i] == PROARGMODE_INOUT ||
argmodes[i] == PROARGMODE_OUT))
{
if (IsA(n, Param))
{
Param *param = (Param *) n;
/* paramid is offset by 1 (see make_datum_param()) */
row->varnos[nfields++] = param->paramid - 1;
}
else
{
/* report error using parameter name, if available */
if (argnames && argnames[i] && argnames[i][0])
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("procedure parameter \"%s\" is an output parameter but corresponding argument is not writable",
argnames[i])));
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("procedure parameter %d is an output parameter but corresponding argument is not writable",
i + 1)));
}
}
i++;
}
row->nfields = nfields;
MemoryContextSwitchTo(oldcontext);
return (PLpgSQL_variable *) row;
}
/* ----------
* exec_stmt_getdiag Put internal PG information into
* specified variables.
@ -2960,7 +2921,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
Assert(query);
if (query->plan == NULL)
exec_prepare_plan(estate, query, curvar->cursor_options, true);
exec_prepare_plan(estate, query, curvar->cursor_options);
/*
* Set up ParamListInfo for this query
@ -3607,12 +3568,13 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
/* static query */
PLpgSQL_expr *expr = stmt->query;
ParamListInfo paramLI;
SPIExecuteOptions options;
/*
* On the first call for this expression generate the plan.
*/
if (expr->plan == NULL)
exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
/*
* Set up ParamListInfo to pass to executor
@ -3622,9 +3584,12 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
/*
* Execute the query
*/
rc = SPI_execute_plan_with_receiver(expr->plan, paramLI,
estate->readonly_func, 0,
treceiver);
memset(&options, 0, sizeof(options));
options.params = paramLI;
options.read_only = estate->readonly_func;
options.dest = treceiver;
rc = SPI_execute_plan_extended(expr->plan, &options);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@ -4091,6 +4056,9 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
else
estate->simple_eval_resowner = shared_simple_eval_resowner;
/* if there's a procedure resowner, it'll be filled in later */
estate->procedure_resowner = NULL;
/*
* We start with no stmt_mcontext; one will be created only if needed.
* That context will be a direct child of the function's main execution
@ -4159,8 +4127,7 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
*/
static void
exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions,
bool keepplan)
PLpgSQL_expr *expr, int cursorOptions)
{
SPIPlanPtr plan;
SPIPrepareOptions options;
@ -4183,8 +4150,8 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
if (plan == NULL)
elog(ERROR, "SPI_prepare_extended failed for \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
if (keepplan)
SPI_keepplan(plan);
SPI_keepplan(plan);
expr->plan = plan;
/* Check to see if it's a simple expression */
@ -4222,7 +4189,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
{
ListCell *l;
exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
stmt->mod_stmt = false;
foreach(l, SPI_plan_get_plan_sources(expr->plan))
{
@ -4681,7 +4648,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
*/
query = stmt->query;
if (query->plan == NULL)
exec_prepare_plan(estate, query, stmt->cursor_options, true);
exec_prepare_plan(estate, query, stmt->cursor_options);
}
else if (stmt->dynquery != NULL)
{
@ -4752,7 +4719,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
query = curvar->cursor_explicit_expr;
if (query->plan == NULL)
exec_prepare_plan(estate, query, curvar->cursor_options, true);
exec_prepare_plan(estate, query, curvar->cursor_options);
}
/*
@ -4985,18 +4952,20 @@ static int
exec_stmt_set(PLpgSQL_execstate *estate, PLpgSQL_stmt_set *stmt)
{
PLpgSQL_expr *expr = stmt->expr;
SPIExecuteOptions options;
int rc;
if (expr->plan == NULL)
{
exec_prepare_plan(estate, expr, 0, true);
expr->plan->no_snapshots = true;
}
exec_prepare_plan(estate, expr, 0);
rc = SPI_execute_plan(expr->plan, NULL, NULL, estate->readonly_func, 0);
memset(&options, 0, sizeof(options));
options.read_only = estate->readonly_func;
options.no_snapshots = true; /* disable SPI's snapshot management */
rc = SPI_execute_plan_extended(expr->plan, &options);
if (rc != SPI_OK_UTILITY)
elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
elog(ERROR, "SPI_execute_plan_extended failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
return PLPGSQL_RC_OK;
@ -5032,7 +5001,7 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
else
expr->target_param = -1; /* should be that already */
exec_prepare_plan(estate, expr, 0, true);
exec_prepare_plan(estate, expr, 0);
}
value = exec_eval_expr(estate, expr, &isnull, &valtype, &valtypmod);
@ -5697,7 +5666,7 @@ exec_eval_expr(PLpgSQL_execstate *estate,
* If first time through, create a plan for this expression.
*/
if (expr->plan == NULL)
exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
/*
* If this is a simple expression, bypass SPI and use the executor
@ -5783,7 +5752,7 @@ exec_run_select(PLpgSQL_execstate *estate,
*/
if (expr->plan == NULL)
exec_prepare_plan(estate, expr,
portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0, true);
portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0);
/*
* Set up ParamListInfo to pass to executor
@ -6056,11 +6025,8 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
*/
if (expr->expr_simple_plan_lxid == curlxid)
{
ResourceOwner saveResourceOwner = CurrentResourceOwner;
CurrentResourceOwner = estate->simple_eval_resowner;
ReleaseCachedPlan(expr->expr_simple_plan, true);
CurrentResourceOwner = saveResourceOwner;
ReleaseCachedPlan(expr->expr_simple_plan,
estate->simple_eval_resowner);
expr->expr_simple_plan = NULL;
expr->expr_simple_plan_lxid = InvalidLocalTransactionId;
}
@ -6094,7 +6060,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
else
{
/* Release SPI_plan_get_cached_plan's refcount */
ReleaseCachedPlan(cplan, true);
ReleaseCachedPlan(cplan, CurrentResourceOwner);
/* Mark expression as non-simple, and fail */
expr->expr_simple_expr = NULL;
expr->expr_rw_param = NULL;
@ -6105,7 +6071,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
* 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);
ReleaseCachedPlan(cplan, CurrentResourceOwner);
/* Extract desired scalar expression from cached plan */
exec_save_simple_expr(expr, cplan);
@ -8023,7 +7989,7 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
* 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, CurrentResourceOwner);
}
/*

View File

@ -951,6 +951,9 @@ stmt_call : K_CALL
new->expr = read_sql_stmt();
new->is_call = true;
/* Remember we may need a procedure resource owner */
plpgsql_curr_compile->requires_procedure_resowner = true;
$$ = (PLpgSQL_stmt *)new;
}
@ -967,6 +970,9 @@ stmt_call : K_CALL
new->expr = read_sql_stmt();
new->is_call = false;
/* Remember we may need a procedure resource owner */
plpgsql_curr_compile->requires_procedure_resowner = true;
$$ = (PLpgSQL_stmt *)new;
}

View File

@ -224,6 +224,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
bool nonatomic;
PLpgSQL_function *func;
PLpgSQL_execstate *save_cur_estate;
ResourceOwner procedure_resowner = NULL;
Datum retval;
int rc;
@ -246,6 +247,17 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
/* Mark the function as busy, so it can't be deleted from under us */
func->use_count++;
/*
* If we'll need a procedure-lifespan resowner to execute any CALL or DO
* statements, create it now. Since this resowner is not tied to any
* parent, failing to free it would result in process-lifespan leaks.
* Therefore, be very wary of adding any code between here and the PG_TRY
* block.
*/
if (nonatomic && func->requires_procedure_resowner)
procedure_resowner =
ResourceOwnerCreate(NULL, "PL/pgSQL procedure resources");
PG_TRY();
{
/*
@ -264,6 +276,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
else
retval = plpgsql_exec_function(func, fcinfo,
NULL, NULL,
procedure_resowner,
!nonatomic);
}
PG_FINALLY();
@ -271,6 +284,13 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
/* Decrement use-count, restore cur_estate */
func->use_count--;
func->cur_estate = save_cur_estate;
/* Be sure to release the procedure resowner if any */
if (procedure_resowner)
{
ResourceOwnerReleaseAllPlanCacheRefs(procedure_resowner);
ResourceOwnerDelete(procedure_resowner);
}
}
PG_END_TRY();
@ -333,6 +353,10 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
* 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.
*
* Because this resowner isn't tied to the calling transaction, we can
* also use it as the "procedure" resowner for any CALL statements. That
* helps reduce the opportunities for failure here.
*/
simple_eval_estate = CreateExecutorState();
simple_eval_resowner =
@ -344,6 +368,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
retval = plpgsql_exec_function(func, fake_fcinfo,
simple_eval_estate,
simple_eval_resowner,
simple_eval_resowner, /* see above */
codeblock->atomic);
}
PG_CATCH();

View File

@ -1009,9 +1009,6 @@ typedef struct PLpgSQL_function
int extra_warnings;
int extra_errors;
/* count of statements inside function */
unsigned int nstatements;
/* the datums representing the function's local variables */
int ndatums;
PLpgSQL_datum **datums;
@ -1020,6 +1017,10 @@ typedef struct PLpgSQL_function
/* function body parsetree */
PLpgSQL_stmt_block *action;
/* data derived while parsing body */
unsigned int nstatements; /* counter for assigning stmtids */
bool requires_procedure_resowner; /* contains CALL or DO? */
/* these fields change when the function is used */
struct PLpgSQL_execstate *cur_estate;
unsigned long use_count;
@ -1081,6 +1082,9 @@ typedef struct PLpgSQL_execstate
EState *simple_eval_estate;
ResourceOwner simple_eval_resowner;
/* if running nonatomic procedure or DO block, resowner to use for CALL */
ResourceOwner procedure_resowner;
/* lookup table to use for executing type casts */
HTAB *cast_hash;
MemoryContext cast_hash_context;
@ -1265,6 +1269,7 @@ extern Datum plpgsql_exec_function(PLpgSQL_function *func,
FunctionCallInfo fcinfo,
EState *simple_eval_estate,
ResourceOwner simple_eval_resowner,
ResourceOwner procedure_resowner,
bool atomic);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
TriggerData *trigdata);