diff options
Diffstat (limited to 'src/backend/executor/spi.c')
-rw-r--r-- | src/backend/executor/spi.c | 363 |
1 files changed, 170 insertions, 193 deletions
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index d71ea60b317..688279c716e 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -56,8 +56,7 @@ static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, bool read_only, bool fire_triggers, long tcount); static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, - Datum *Values, const char *Nulls, - int pflags); + Datum *Values, const char *Nulls); static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount); @@ -67,7 +66,7 @@ static void _SPI_cursor_operation(Portal portal, FetchDirection direction, long count, DestReceiver *dest); -static SPIPlanPtr _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt); +static SPIPlanPtr _SPI_make_plan_non_temp(SPIPlanPtr plan); static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan); static int _SPI_begin_call(bool execmem); @@ -391,8 +390,7 @@ SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, res = _SPI_execute_plan(plan, _SPI_convert_params(plan->nargs, plan->argtypes, - Values, Nulls, - 0), + Values, Nulls), InvalidSnapshot, InvalidSnapshot, read_only, true, tcount); @@ -462,8 +460,7 @@ SPI_execute_snapshot(SPIPlanPtr plan, res = _SPI_execute_plan(plan, _SPI_convert_params(plan->nargs, plan->argtypes, - Values, Nulls, - 0), + Values, Nulls), snapshot, crosscheck_snapshot, read_only, fire_triggers, tcount); @@ -474,11 +471,8 @@ SPI_execute_snapshot(SPIPlanPtr plan, /* * SPI_execute_with_args -- plan and execute a query with supplied arguments * - * This is functionally comparable to SPI_prepare followed by - * SPI_execute_plan, except that since we know the plan will be used only - * once, we can tell the planner to rely on the parameter values as constants. - * This eliminates potential performance disadvantages compared to - * inserting the parameter values directly into the query text. + * This is functionally equivalent to SPI_prepare followed by + * SPI_execute_plan. */ int SPI_execute_with_args(const char *src, @@ -509,13 +503,10 @@ SPI_execute_with_args(const char *src, plan.parserSetupArg = NULL; paramLI = _SPI_convert_params(nargs, argtypes, - Values, Nulls, - PARAM_FLAG_CONST); + Values, Nulls); _SPI_prepare_plan(src, &plan, paramLI); - /* We don't need to copy the plan since it will be thrown away anyway */ - res = _SPI_execute_plan(&plan, paramLI, InvalidSnapshot, InvalidSnapshot, read_only, true, tcount); @@ -558,7 +549,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, _SPI_prepare_plan(src, &plan, NULL); /* copy plan to procedure context */ - result = _SPI_copy_plan(&plan, _SPI_current->procCxt); + result = _SPI_make_plan_non_temp(&plan); _SPI_end_call(true); @@ -595,20 +586,45 @@ SPI_prepare_params(const char *src, _SPI_prepare_plan(src, &plan, NULL); /* copy plan to procedure context */ - result = _SPI_copy_plan(&plan, _SPI_current->procCxt); + result = _SPI_make_plan_non_temp(&plan); _SPI_end_call(true); return result; } +int +SPI_keepplan(SPIPlanPtr plan) +{ + ListCell *lc; + + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved) + return SPI_ERROR_ARGUMENT; + + /* + * Mark it saved, reparent it under CacheMemoryContext, and mark all the + * component CachedPlanSources as saved. This sequence cannot fail + * partway through, so there's no risk of long-term memory leakage. + */ + plan->saved = true; + MemoryContextSetParent(plan->plancxt, CacheMemoryContext); + + foreach(lc, plan->plancache_list) + { + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + + SaveCachedPlan(plansource); + } + + return 0; +} + SPIPlanPtr SPI_saveplan(SPIPlanPtr plan) { SPIPlanPtr newplan; - /* We don't currently support copying an already-saved plan */ - if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved) + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) { SPI_result = SPI_ERROR_ARGUMENT; return NULL; @@ -620,8 +636,7 @@ SPI_saveplan(SPIPlanPtr plan) newplan = _SPI_save_plan(plan); - _SPI_curid--; - SPI_result = 0; + SPI_result = _SPI_end_call(false); return newplan; } @@ -629,20 +644,17 @@ SPI_saveplan(SPIPlanPtr plan) int SPI_freeplan(SPIPlanPtr plan) { + ListCell *lc; + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) return SPI_ERROR_ARGUMENT; - /* If plancache.c owns the plancache entries, we must release them */ - if (plan->saved) + /* Release the plancache entries */ + foreach(lc, plan->plancache_list) { - ListCell *lc; - - foreach(lc, plan->plancache_list) - { - CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); - DropCachedPlan(plansource); - } + DropCachedPlan(plansource); } /* Now get rid of the _SPI_plan and subsidiary data in its plancxt */ @@ -1020,8 +1032,7 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan, /* build transient ParamListInfo in caller's context */ paramLI = _SPI_convert_params(plan->nargs, plan->argtypes, - Values, Nulls, - 0); + Values, Nulls); portal = SPI_cursor_open_internal(name, plan, paramLI, read_only); @@ -1036,9 +1047,7 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan, /* * SPI_cursor_open_with_args() * - * Parse and plan a query and open it as a portal. Like SPI_execute_with_args, - * we can tell the planner to rely on the parameter values as constants, - * because the plan will only be used once. + * Parse and plan a query and open it as a portal. */ Portal SPI_cursor_open_with_args(const char *name, @@ -1071,8 +1080,7 @@ SPI_cursor_open_with_args(const char *name, /* build transient ParamListInfo in executor context */ paramLI = _SPI_convert_params(nargs, argtypes, - Values, Nulls, - PARAM_FLAG_CONST); + Values, Nulls); _SPI_prepare_plan(src, &plan, paramLI); @@ -1081,9 +1089,6 @@ SPI_cursor_open_with_args(const char *name, /* Adjust stack so that SPI_cursor_open_internal doesn't complain */ _SPI_curid--; - /* SPI_cursor_open_internal must be called in procedure memory context */ - _SPI_procmem(); - result = SPI_cursor_open_internal(name, &plan, paramLI, read_only); /* And clean up */ @@ -1148,7 +1153,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, plansource = (CachedPlanSource *) linitial(plan->plancache_list); /* Push the SPI stack */ - if (_SPI_begin_call(false) < 0) + if (_SPI_begin_call(true) < 0) elog(ERROR, "SPI_cursor_open called while not connected"); /* Reset SPI result (note we deliberately don't touch lastoid) */ @@ -1174,22 +1179,27 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, plansource->query_string); /* - * Note: we mustn't have any failure occur between RevalidateCachedPlan - * and PortalDefineQuery; that would result in leaking our plancache - * refcount. + * Note: for a saved plan, we mustn't have any failure occur between + * GetCachedPlan and PortalDefineQuery; that would result in leaking our + * plancache refcount. */ - if (plan->saved) - { - /* Replan if needed, and increment plan refcount for portal */ - cplan = RevalidateCachedPlan(plansource, false); - stmt_list = cplan->stmt_list; - } - else + + /* Replan if needed, and increment plan refcount for portal */ + cplan = GetCachedPlan(plansource, paramLI, false); + stmt_list = cplan->stmt_list; + + if (!plan->saved) { - /* No replan, but copy the plan into the portal's context */ + /* + * We don't want the portal to depend on an unsaved CachedPlanSource, + * so must copy the plan into the portal's context. An error here + * will result in leaking our refcount on the plan, but it doesn't + * matter because the plan is unsaved and hence transient anyway. + */ oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); - stmt_list = copyObject(plansource->plan->stmt_list); + stmt_list = copyObject(stmt_list); MemoryContextSwitchTo(oldcontext); + ReleaseCachedPlan(cplan, false); cplan = NULL; /* portal shouldn't depend on cplan */ } @@ -1238,9 +1248,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, /* * If told to be read-only, we'd better check for read-only queries. This * can't be done earlier because we need to look at the finished, planned - * queries. (In particular, we don't want to do it between - * RevalidateCachedPlan and PortalDefineQuery, because throwing an error - * between those steps would result in leaking our plancache refcount.) + * queries. (In particular, we don't want to do it between GetCachedPlan + * and PortalDefineQuery, because throwing an error between those steps + * would result in leaking our plancache refcount.) */ if (read_only) { @@ -1288,7 +1298,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, Assert(portal->strategy != PORTAL_MULTI_QUERY); /* Pop the SPI stack */ - _SPI_end_call(false); + _SPI_end_call(true); /* Return the created portal */ return portal; @@ -1420,7 +1430,6 @@ bool SPI_is_cursor_plan(SPIPlanPtr plan) { CachedPlanSource *plansource; - CachedPlan *cplan; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) { @@ -1435,19 +1444,11 @@ SPI_is_cursor_plan(SPIPlanPtr plan) } plansource = (CachedPlanSource *) linitial(plan->plancache_list); - /* Need _SPI_begin_call in case replanning invokes SPI-using functions */ - SPI_result = _SPI_begin_call(false); - if (SPI_result < 0) - return false; - - if (plan->saved) - { - /* Make sure the plan is up to date */ - cplan = RevalidateCachedPlan(plansource, true); - ReleaseCachedPlan(cplan, true); - } - - _SPI_end_call(false); + /* + * We used to force revalidation of the cached plan here, but that seems + * unnecessary: invalidation could mean a change in the rowtype of the + * tuples returned by a plan, but not whether it returns tuples at all. + */ SPI_result = 0; /* Does it return tuples? */ @@ -1466,25 +1467,18 @@ SPI_is_cursor_plan(SPIPlanPtr plan) bool SPI_plan_is_valid(SPIPlanPtr plan) { - Assert(plan->magic == _SPI_PLAN_MAGIC); - if (plan->saved) - { - ListCell *lc; + ListCell *lc; - foreach(lc, plan->plancache_list) - { - CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + Assert(plan->magic == _SPI_PLAN_MAGIC); - if (!CachedPlanIsValid(plansource)) - return false; - } - return true; - } - else + foreach(lc, plan->plancache_list) { - /* An unsaved plan is assumed valid for its (short) lifetime */ - return true; + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + + if (!CachedPlanIsValid(plansource)) + return false; } + return true; } /* @@ -1646,7 +1640,7 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) */ /* - * Parse and plan a querystring. + * Parse and analyze a querystring. * * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup * and plan->parserSetupArg) must be valid, as must plan->cursor_options. @@ -1656,8 +1650,10 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) * param type information embedded in the plan! * * Results are stored into *plan (specifically, plan->plancache_list). - * Note however that the result trees are all in CurrentMemoryContext - * and need to be copied somewhere to survive. + * Note that the result data is all in CurrentMemoryContext or child contexts + * thereof; in practice this means it is in the SPI executor context, and + * what we are creating is a "temporary" SPIPlan. Cruft generated during + * parsing is also left in CurrentMemoryContext. */ static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) @@ -1682,8 +1678,8 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) raw_parsetree_list = pg_parse_query(src); /* - * Do parse analysis, rule rewrite, and planning for each raw parsetree, - * then cons up a phony plancache entry for each one. + * Do parse analysis and rule rewrite for each raw parsetree, storing + * the results into unsaved plancache entries. */ plancache_list = NIL; @@ -1692,7 +1688,14 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) Node *parsetree = (Node *) lfirst(list_item); List *stmt_list; CachedPlanSource *plansource; - CachedPlan *cplan; + + /* + * Create the CachedPlanSource before we do parse analysis, since + * it needs to see the unmodified raw parse tree. + */ + plansource = CreateCachedPlan(parsetree, + src, + CreateCommandTag(parsetree)); /* * Parameter datatypes are driven by parserSetup hook if provided, @@ -1701,41 +1704,29 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) if (plan->parserSetup != NULL) { Assert(plan->nargs == 0); - /* Need a copyObject here to keep parser from modifying raw tree */ - stmt_list = pg_analyze_and_rewrite_params(copyObject(parsetree), + stmt_list = pg_analyze_and_rewrite_params(parsetree, src, plan->parserSetup, plan->parserSetupArg); } else { - /* Need a copyObject here to keep parser from modifying raw tree */ - stmt_list = pg_analyze_and_rewrite(copyObject(parsetree), + stmt_list = pg_analyze_and_rewrite(parsetree, src, plan->argtypes, plan->nargs); } - stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams); - - plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); - cplan = (CachedPlan *) palloc0(sizeof(CachedPlan)); - - plansource->raw_parse_tree = parsetree; - /* cast-away-const here is a bit ugly, but there's no reason to copy */ - plansource->query_string = (char *) src; - plansource->commandTag = CreateCommandTag(parsetree); - plansource->param_types = plan->argtypes; - plansource->num_params = plan->nargs; - plansource->parserSetup = plan->parserSetup; - plansource->parserSetupArg = plan->parserSetupArg; - plansource->fully_planned = true; - plansource->fixed_result = false; - /* no need to set search_path, generation or saved_xmin */ - plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list); - plansource->plan = cplan; - - cplan->stmt_list = stmt_list; - cplan->fully_planned = true; + + /* Finish filling in the CachedPlanSource */ + CompleteCachedPlan(plansource, + stmt_list, + NULL, + plan->argtypes, + plan->nargs, + plan->parserSetup, + plan->parserSetupArg, + cursor_options, + false); /* not fixed result */ plancache_list = lappend(plancache_list, plansource); } @@ -1824,18 +1815,12 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, spierrcontext.arg = (void *) plansource->query_string; - if (plan->saved) - { - /* Replan if needed, and increment plan refcount locally */ - cplan = RevalidateCachedPlan(plansource, true); - stmt_list = cplan->stmt_list; - } - else - { - /* No replan here */ - cplan = NULL; - stmt_list = plansource->plan->stmt_list; - } + /* + * Replan if needed, and increment plan refcount. If it's a saved + * plan, the refcount must be backed by the CurrentResourceOwner. + */ + cplan = GetCachedPlan(plansource, paramLI, plan->saved); + stmt_list = cplan->stmt_list; /* * In the default non-read-only case, get a new snapshot, replacing @@ -1966,8 +1951,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, } /* Done with this plan, so release refcount */ - if (cplan) - ReleaseCachedPlan(cplan, true); + ReleaseCachedPlan(cplan, plan->saved); cplan = NULL; /* @@ -1987,7 +1971,7 @@ fail: /* We no longer need the cached plan refcount, if any */ if (cplan) - ReleaseCachedPlan(cplan, true); + ReleaseCachedPlan(cplan, plan->saved); /* * Pop the error context stack @@ -2018,8 +2002,7 @@ fail: */ static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, - Datum *Values, const char *Nulls, - int pflags) + Datum *Values, const char *Nulls) { ParamListInfo paramLI; @@ -2043,7 +2026,7 @@ _SPI_convert_params(int nargs, Oid *argtypes, prm->value = Values[i]; prm->isnull = (Nulls && Nulls[i] == 'n'); - prm->pflags = pflags; + prm->pflags = PARAM_FLAG_CONST; prm->ptype = argtypes[i]; } } @@ -2283,21 +2266,31 @@ _SPI_checktuples(void) } /* - * Make an "unsaved" copy of the given plan, in a child context of parentcxt. + * Convert a "temporary" SPIPlan into an "unsaved" plan. + * + * The passed _SPI_plan struct is on the stack, and all its subsidiary data + * is in or under the current SPI executor context. Copy the plan into the + * SPI procedure context so it will survive _SPI_end_call(). To minimize + * data copying, this destructively modifies the input plan, by taking the + * plancache entries away from it and reparenting them to the new SPIPlan. */ static SPIPlanPtr -_SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) +_SPI_make_plan_non_temp(SPIPlanPtr plan) { SPIPlanPtr newplan; + MemoryContext parentcxt = _SPI_current->procCxt; MemoryContext plancxt; MemoryContext oldcxt; ListCell *lc; - Assert(!plan->saved); /* not currently supported */ + /* Assert the input is a temporary SPIPlan */ + Assert(plan->magic == _SPI_PLAN_MAGIC); + Assert(plan->plancxt == NULL); /* - * Create a memory context for the plan. We don't expect the plan to be - * very large, so use smaller-than-default alloc parameters. + * Create a memory context for the plan, underneath the procedure context. + * We don't expect the plan to be very large, so use smaller-than-default + * alloc parameters. */ plancxt = AllocSetContextCreate(parentcxt, "SPI Plan", @@ -2306,7 +2299,7 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) ALLOCSET_SMALL_MAXSIZE); oldcxt = MemoryContextSwitchTo(plancxt); - /* Copy the SPI plan into its own context */ + /* Copy the SPI_plan struct and subsidiary data into the new context */ newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; newplan->saved = false; @@ -2324,46 +2317,32 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) newplan->parserSetup = plan->parserSetup; newplan->parserSetupArg = plan->parserSetupArg; + /* + * Reparent all the CachedPlanSources into the procedure context. In + * theory this could fail partway through due to the pallocs, but we + * don't care too much since both the procedure context and the executor + * context would go away on error. + */ foreach(lc, plan->plancache_list) { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); - CachedPlanSource *newsource; - CachedPlan *cplan; - CachedPlan *newcplan; - - /* Note: we assume we don't need to revalidate the plan */ - cplan = plansource->plan; - - newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); - newcplan = (CachedPlan *) palloc0(sizeof(CachedPlan)); - - newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree); - newsource->query_string = pstrdup(plansource->query_string); - newsource->commandTag = plansource->commandTag; - newsource->param_types = newplan->argtypes; - newsource->num_params = newplan->nargs; - newsource->parserSetup = newplan->parserSetup; - newsource->parserSetupArg = newplan->parserSetupArg; - newsource->fully_planned = plansource->fully_planned; - newsource->fixed_result = plansource->fixed_result; - /* no need to worry about seach_path, generation or saved_xmin */ - if (plansource->resultDesc) - newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc); - newsource->plan = newcplan; - - newcplan->stmt_list = copyObject(cplan->stmt_list); - newcplan->fully_planned = cplan->fully_planned; - newplan->plancache_list = lappend(newplan->plancache_list, newsource); + CachedPlanSetParentContext(plansource, parentcxt); + + /* Build new list, with list cells in plancxt */ + newplan->plancache_list = lappend(newplan->plancache_list, plansource); } MemoryContextSwitchTo(oldcxt); + /* For safety, unlink the CachedPlanSources from the temporary plan */ + plan->plancache_list = NIL; + return newplan; } /* - * Make a "saved" copy of the given plan, entrusting everything to plancache.c + * Make a "saved" copy of the given plan. */ static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan) @@ -2373,13 +2352,12 @@ _SPI_save_plan(SPIPlanPtr plan) MemoryContext oldcxt; ListCell *lc; - Assert(!plan->saved); /* not currently supported */ - /* * Create a memory context for the plan. We don't expect the plan to be - * very large, so use smaller-than-default alloc parameters. + * very large, so use smaller-than-default alloc parameters. It's a + * transient context until we finish copying everything. */ - plancxt = AllocSetContextCreate(CacheMemoryContext, + plancxt = AllocSetContextCreate(CurrentMemoryContext, "SPI Plan", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, @@ -2389,7 +2367,7 @@ _SPI_save_plan(SPIPlanPtr plan) /* Copy the SPI plan into its own context */ newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; - newplan->saved = true; + newplan->saved = false; newplan->plancache_list = NIL; newplan->plancxt = plancxt; newplan->cursor_options = plan->cursor_options; @@ -2404,33 +2382,32 @@ _SPI_save_plan(SPIPlanPtr plan) newplan->parserSetup = plan->parserSetup; newplan->parserSetupArg = plan->parserSetupArg; + /* Copy all the plancache entries */ foreach(lc, plan->plancache_list) { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); CachedPlanSource *newsource; - CachedPlan *cplan; - - /* Note: we assume we don't need to revalidate the plan */ - cplan = plansource->plan; - - newsource = CreateCachedPlan(plansource->raw_parse_tree, - plansource->query_string, - plansource->commandTag, - newplan->argtypes, - newplan->nargs, - newplan->cursor_options, - cplan->stmt_list, - true, - false); - if (newplan->parserSetup != NULL) - CachedPlanSetParserHook(newsource, - newplan->parserSetup, - newplan->parserSetupArg); + newsource = CopyCachedPlan(plansource); newplan->plancache_list = lappend(newplan->plancache_list, newsource); } MemoryContextSwitchTo(oldcxt); + /* + * Mark it saved, reparent it under CacheMemoryContext, and mark all the + * component CachedPlanSources as saved. This sequence cannot fail + * partway through, so there's no risk of long-term memory leakage. + */ + newplan->saved = true; + MemoryContextSetParent(newplan->plancxt, CacheMemoryContext); + + foreach(lc, newplan->plancache_list) + { + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + + SaveCachedPlan(plansource); + } + return newplan; } |