diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml index f5e0a35da0..d8c121f5f3 100644 --- a/doc/src/sgml/spi.sgml +++ b/doc/src/sgml/spi.sgml @@ -1722,6 +1722,172 @@ int SPI_execute_plan(SPIPlanPtr plan, Datum * + + SPI_execute_plan_extended + + + SPI_execute_plan_extended + 3 + + + + SPI_execute_plan_extended + execute a statement prepared by SPI_prepare + + + + +int SPI_execute_plan_extended(SPIPlanPtr plan, + const SPIExecuteOptions * options) + + + + + Description + + + SPI_execute_plan_extended executes a statement + prepared by SPI_prepare or one of its siblings. + This function is equivalent to SPI_execute_plan, + except that information about the parameter values to be passed to the + query is presented differently, and additional execution-controlling + options can be passed. + + + + Query parameter values are represented by + a ParamListInfo 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 ParamListInfo. + + + + Also, instead of always accumulating the result tuples into a + SPI_tuptable structure, tuples can be passed to a + caller-supplied DestReceiver 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. + + + + + Arguments + + + + SPIPlanPtr plan + + + prepared statement (returned by SPI_prepare) + + + + + + const SPIExecuteOptions * options + + + struct containing optional arguments + + + + + + + Callers should always zero out the entire options + 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 options fields are: + + + + + ParamListInfo params + + + data structure containing query parameter types and values; NULL if none + + + + + + bool read_only + + true for read-only execution + + + + + bool no_snapshots + + + true prevents SPI from managing snapshots for + execution of the query; use with extreme caution + + + + + + uint64 tcount + + + maximum number of rows to return, + or 0 for no limit + + + + + + DestReceiver * dest + + + DestReceiver object that will receive any tuples + emitted by the query; if NULL, result tuples are accumulated into + a SPI_tuptable structure, as + in SPI_execute_plan + + + + + + ResourceOwner owner + + + 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. + + + + + + + + Return Value + + + The return value is the same as for SPI_execute_plan. + + + + When dest is NULL, + SPI_processed and + SPI_tuptable are set as in + SPI_execute_plan. + When dest is not NULL, + SPI_processed is set to zero and + SPI_tuptable is set to NULL. If a tuple count + is required, the caller's DestReceiver object must + calculate it. + + + + + + SPI_execute_plan_with_paramlist @@ -1757,6 +1923,11 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr plan, already available in that format. It also supports use of dynamic parameter sets via hook functions specified in ParamListInfo. + + + This function is now deprecated in favor + of SPI_execute_plan_extended. + @@ -1817,120 +1988,6 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr plan, - - SPI_execute_plan_with_receiver - - - SPI_execute_plan_with_receiver - 3 - - - - SPI_execute_plan_with_receiver - execute a statement prepared by SPI_prepare - - - - -int SPI_execute_plan_with_receiver(SPIPlanPtr plan, - ParamListInfo params, - bool read_only, - long count, - DestReceiver *dest) - - - - - Description - - - SPI_execute_plan_with_receiver executes a statement - prepared by SPI_prepare. This function is - equivalent to SPI_execute_plan_with_paramlist - except that, instead of always accumulating the result tuples into a - SPI_tuptable structure, tuples can be passed to a - caller-supplied DestReceiver 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. - - - - - Arguments - - - - SPIPlanPtr plan - - - prepared statement (returned by SPI_prepare) - - - - - - ParamListInfo params - - - data structure containing parameter types and values; NULL if none - - - - - - bool read_only - - true for read-only execution - - - - - long count - - - maximum number of rows to return, - or 0 for no limit - - - - - - DestReceiver * dest - - - DestReceiver object that will receive any tuples - emitted by the query; if NULL, this function is exactly equivalent to - SPI_execute_plan_with_paramlist - - - - - - - - Return Value - - - The return value is the same as for SPI_execute_plan. - - - - When dest is NULL, - SPI_processed and - SPI_tuptable are set as in - SPI_execute_plan. - When dest is not NULL, - SPI_processed is set to zero and - SPI_tuptable is set to NULL. If a tuple count - is required, the caller's DestReceiver object must - calculate it. - - - - - - SPI_execp diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 653ef8e41a..f767751c71 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 e28d242922..68a6bcea02 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; @@ -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 diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 8dab9fd578..cb5a96117f 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 cc04b5b4be..1a0950489d 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 22c6d2ba5b..66e3181815 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 10f15f6a35..a171df573c 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 9c70603434..5740f8956e 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 ce0f58ce68..97f4279ac4 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 79d96e5ed0..ff09c63a02 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 5336793a93..ce8d97447d 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 3a9349b724..383d92fc1d 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); - } - - 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); } /* diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 0b0ff4e5de..abf196d4be 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 52e6c69cc5..97f4264c28 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 416fda7f3b..d5010862a5 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);