aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/prepare.c7
-rw-r--r--src/backend/executor/spi.c106
-rw-r--r--src/backend/tcop/postgres.c2
-rw-r--r--src/backend/utils/cache/plancache.c32
-rw-r--r--src/backend/utils/mmgr/portalmem.c2
-rw-r--r--src/backend/utils/resowner/resowner.c8
-rw-r--r--src/include/executor/spi.h17
-rw-r--r--src/include/executor/spi_priv.h1
-rw-r--r--src/include/utils/plancache.h4
-rw-r--r--src/pl/plpgsql/src/pl_comp.c2
-rw-r--r--src/pl/plpgsql/src/pl_exec.c440
-rw-r--r--src/pl/plpgsql/src/pl_gram.y6
-rw-r--r--src/pl/plpgsql/src/pl_handler.c25
-rw-r--r--src/pl/plpgsql/src/plpgsql.h11
14 files changed, 350 insertions, 313 deletions
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 653ef8e41a6..f767751c71a 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -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);
}
/*
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index e28d2429222..68a6bcea02d 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -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;
@@ -570,37 +576,32 @@ SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount)
/* Execute a previously prepared plan */
int
-SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params,
- bool read_only, long tcount)
+SPI_execute_plan_extended(SPIPlanPtr plan,
+ const SPIExecuteOptions *options)
{
int res;
- if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
+ 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, params,
+ res = _SPI_execute_plan(plan, options->params,
InvalidSnapshot, InvalidSnapshot,
- read_only, true, tcount, NULL);
+ options->read_only, options->no_snapshots,
+ true, options->tcount,
+ options->dest, options->owner);
_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.
- */
+/* Execute a previously prepared plan */
int
-SPI_execute_plan_with_receiver(SPIPlanPtr plan,
- ParamListInfo params,
- bool read_only, long tcount,
- DestReceiver *dest)
+SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params,
+ bool read_only, long tcount)
{
int res;
@@ -613,7 +614,9 @@ SPI_execute_plan_with_receiver(SPIPlanPtr plan,
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
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 8dab9fd5780..cb5a96117f6 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -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.
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index cc04b5b4bef..1a0950489d7 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -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--;
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 22c6d2ba5b2..66e31818156 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -310,7 +310,7 @@ PortalReleaseCachedPlan(Portal portal)
{
if (portal->cplan)
{
- ReleaseCachedPlan(portal->cplan, false);
+ ReleaseCachedPlan(portal->cplan, NULL);
portal->cplan = NULL;
/*
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 10f15f6a357..a171df573ce 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -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;
}
/*
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 9c70603434a..5740f8956e5 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -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);
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index ce0f58ce687..97f4279ac4a 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -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 */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 79d96e5ed03..ff09c63a02f 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -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,
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 5336793a933..ce8d97447d1 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -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);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 3a9349b7242..383d92fc1d0 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -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);
- }
+ if (stmt->is_call)
+ stmt->target = make_callstmt_target(estate, expr);
+ }
- paramLI = setup_param_list(estate, expr);
+ paramLI = setup_param_list(estate, expr);
- before_lxid = MyProc->lxid;
+ 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);
- }
- PG_CATCH();
+ /*
+ * 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 (!estate->readonly_func)
{
- /*
- * If we are using a local plan, restore the old plan link.
- */
- if (local_plan)
- expr->plan = orig_plan;
- PG_RE_THROW();
+ PushActiveSnapshot(GetTransactionSnapshot());
+ pushed_active_snap = true;
}
- PG_END_TRY();
/*
- * 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.)
+ * 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.
*/
- if (local_plan)
- {
- SPIPlanPtr plan = expr->plan;
+ 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;
- expr->plan = orig_plan;
- SPI_freeplan(plan);
- }
+ 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);
+
+ memset(&options, 0, sizeof(options));
+ options.read_only = estate->readonly_func;
+ options.no_snapshots = true; /* disable SPI's snapshot management */
- rc = SPI_execute_plan(expr->plan, NULL, NULL, estate->readonly_func, 0);
+ 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);
}
/*
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 0b0ff4e5de2..abf196d4be1 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -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;
}
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 52e6c69cc55..97f4264c287 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -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();
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 416fda7f3b5..d5010862a50 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -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);