diff options
Diffstat (limited to 'src/backend/utils/cache/plancache.c')
-rw-r--r-- | src/backend/utils/cache/plancache.c | 191 |
1 files changed, 162 insertions, 29 deletions
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index f509f96ca7f..cbc7c498d0d 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -181,6 +181,7 @@ CreateCachedPlan(Node *raw_parse_tree, plansource->invalItems = NIL; plansource->query_context = NULL; plansource->gplan = NULL; + plansource->is_oneshot = false; plansource->is_complete = false; plansource->is_saved = false; plansource->is_valid = false; @@ -196,6 +197,69 @@ CreateCachedPlan(Node *raw_parse_tree, } /* + * CreateOneShotCachedPlan: initially create a one-shot plan cache entry. + * + * This variant of CreateCachedPlan creates a plan cache entry that is meant + * to be used only once. No data copying occurs: all data structures remain + * in the caller's memory context (which typically should get cleared after + * completing execution). The CachedPlanSource struct itself is also created + * in that context. + * + * A one-shot plan cannot be saved or copied, since we make no effort to + * preserve the raw parse tree unmodified. There is also no support for + * invalidation, so plan use must be completed in the current transaction, + * and DDL that might invalidate the querytree_list must be avoided as well. + * + * raw_parse_tree: output of raw_parser() + * query_string: original query text + * commandTag: compile-time-constant tag for query, or NULL if empty query + */ +CachedPlanSource * +CreateOneShotCachedPlan(Node *raw_parse_tree, + const char *query_string, + const char *commandTag) +{ + CachedPlanSource *plansource; + + Assert(query_string != NULL); /* required as of 8.4 */ + + /* + * Create and fill the CachedPlanSource struct within the caller's memory + * context. Most fields are just left empty for the moment. + */ + plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); + plansource->magic = CACHEDPLANSOURCE_MAGIC; + plansource->raw_parse_tree = raw_parse_tree; + plansource->query_string = query_string; + plansource->commandTag = commandTag; + plansource->param_types = NULL; + plansource->num_params = 0; + plansource->parserSetup = NULL; + plansource->parserSetupArg = NULL; + plansource->cursor_options = 0; + plansource->fixed_result = false; + plansource->resultDesc = NULL; + plansource->search_path = NULL; + plansource->context = CurrentMemoryContext; + plansource->query_list = NIL; + plansource->relationOids = NIL; + plansource->invalItems = NIL; + plansource->query_context = NULL; + plansource->gplan = NULL; + plansource->is_oneshot = true; + plansource->is_complete = false; + plansource->is_saved = false; + plansource->is_valid = false; + plansource->generation = 0; + plansource->next_saved = NULL; + plansource->generic_cost = -1; + plansource->total_custom_cost = 0; + plansource->num_custom_plans = 0; + + return plansource; +} + +/* * CompleteCachedPlan: second step of creating a plan cache entry. * * Pass in the analyzed-and-rewritten form of the query, as well as the @@ -222,6 +286,10 @@ CreateCachedPlan(Node *raw_parse_tree, * option, it is caller's responsibility that the referenced data remains * valid for as long as the CachedPlanSource exists. * + * If the CachedPlanSource is a "oneshot" plan, then no querytree copying + * occurs at all, and querytree_context is ignored; it is caller's + * responsibility that the passed querytree_list is sufficiently long-lived. + * * plansource: structure returned by CreateCachedPlan * querytree_list: analyzed-and-rewritten form of query (list of Query nodes) * querytree_context: memory context containing querytree_list, @@ -254,9 +322,15 @@ CompleteCachedPlan(CachedPlanSource *plansource, /* * If caller supplied a querytree_context, reparent it underneath the * CachedPlanSource's context; otherwise, create a suitable context and - * copy the querytree_list into it. + * copy the querytree_list into it. But no data copying should be done + * for one-shot plans; for those, assume the passed querytree_list is + * sufficiently long-lived. */ - if (querytree_context != NULL) + if (plansource->is_oneshot) + { + querytree_context = CurrentMemoryContext; + } + else if (querytree_context != NULL) { MemoryContextSetParent(querytree_context, source_context); MemoryContextSwitchTo(querytree_context); @@ -279,11 +353,12 @@ CompleteCachedPlan(CachedPlanSource *plansource, /* * Use the planner machinery to extract dependencies. Data is saved in * query_context. (We assume that not a lot of extra cruft is created by - * this call.) + * this call.) We can skip this for one-shot plans. */ - extract_query_dependencies((Node *) querytree_list, - &plansource->relationOids, - &plansource->invalItems); + if (!plansource->is_oneshot) + extract_query_dependencies((Node *) querytree_list, + &plansource->relationOids, + &plansource->invalItems); /* * Save the final parameter types (or other parameter specification data) @@ -326,7 +401,8 @@ CompleteCachedPlan(CachedPlanSource *plansource, * it to the list of cached plans that are checked for invalidation when an * sinval event occurs. * - * This is guaranteed not to throw error; callers typically depend on that + * This is guaranteed not to throw error, except for the caller-error case + * of trying to save a one-shot plan. Callers typically depend on that * since this is called just before or just after adding a pointer to the * CachedPlanSource to some permanent data structure of their own. Up until * this is done, a CachedPlanSource is just transient data that will go away @@ -340,6 +416,10 @@ SaveCachedPlan(CachedPlanSource *plansource) Assert(plansource->is_complete); Assert(!plansource->is_saved); + /* This seems worth a real test, though */ + if (plansource->is_oneshot) + elog(ERROR, "cannot save one-shot cached plan"); + /* * In typical use, this function would be called before generating any * plans from the CachedPlanSource. If there is a generic plan, moving it @@ -402,11 +482,15 @@ DropCachedPlan(CachedPlanSource *plansource) /* Decrement generic CachePlan's refcount and drop if no longer needed */ ReleaseGenericPlan(plansource); + /* Mark it no longer valid */ + plansource->magic = 0; + /* * Remove the CachedPlanSource and all subsidiary data (including the - * query_context if any). + * query_context if any). But if it's a one-shot we can't free anything. */ - MemoryContextDelete(plansource->context); + if (!plansource->is_oneshot) + MemoryContextDelete(plansource->context); } /* @@ -452,6 +536,17 @@ RevalidateCachedQuery(CachedPlanSource *plansource) MemoryContext oldcxt; /* + * For one-shot plans, we do not support revalidation checking; it's + * assumed the query is parsed, planned, and executed in one transaction, + * so that no lock re-acquisition is necessary. + */ + if (plansource->is_oneshot) + { + Assert(plansource->is_valid); + return NIL; + } + + /* * If the query is currently valid, acquire locks on the referenced * objects; then check again. We need to do it this way to cover the race * condition that an invalidation message arrives before we get the locks. @@ -649,6 +744,8 @@ CheckCachedPlan(CachedPlanSource *plansource) return false; Assert(plan->magic == CACHEDPLAN_MAGIC); + /* Generic plans are never one-shot */ + Assert(!plan->is_oneshot); /* * If it appears valid, acquire locks and recheck; this is much the same @@ -708,7 +805,8 @@ CheckCachedPlan(CachedPlanSource *plansource) * hint rather than a hard constant. * * Planning work is done in the caller's memory context. The finished plan - * is in a child memory context, which typically should get reparented. + * is in a child memory context, which typically should get reparented + * (unless this is a one-shot plan, in which case we don't copy the plan). */ static CachedPlan * BuildCachedPlan(CachedPlanSource *plansource, List *qlist, @@ -719,7 +817,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, bool snapshot_set; bool spi_pushed; MemoryContext plan_context; - MemoryContext oldcxt; + MemoryContext oldcxt = CurrentMemoryContext; /* * Normally the querytree should be valid already, but if it's not, @@ -739,10 +837,16 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, /* * If we don't already have a copy of the querytree list that can be - * scribbled on by the planner, make one. + * scribbled on by the planner, make one. For a one-shot plan, we assume + * it's okay to scribble on the original query_list. */ if (qlist == NIL) - qlist = (List *) copyObject(plansource->query_list); + { + if (!plansource->is_oneshot) + qlist = (List *) copyObject(plansource->query_list); + else + qlist = plansource->query_list; + } /* * Restore the search_path that was in use when the plan was made. See @@ -794,22 +898,29 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, PopOverrideSearchPath(); /* - * Make a dedicated memory context for the CachedPlan and its subsidiary - * data. It's probably not going to be large, but just in case, use the - * default maxsize parameter. It's transient for the moment. + * Normally we make a dedicated memory context for the CachedPlan and its + * subsidiary data. (It's probably not going to be large, but just in + * case, use the default maxsize parameter. It's transient for the + * moment.) But for a one-shot plan, we just leave it in the caller's + * memory context. */ - plan_context = AllocSetContextCreate(CurrentMemoryContext, - "CachedPlan", - ALLOCSET_SMALL_MINSIZE, - ALLOCSET_SMALL_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + if (!plansource->is_oneshot) + { + plan_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlan", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); - /* - * Copy plan into the new context. - */ - oldcxt = MemoryContextSwitchTo(plan_context); + /* + * Copy plan into the new context. + */ + MemoryContextSwitchTo(plan_context); - plist = (List *) copyObject(plist); + plist = (List *) copyObject(plist); + } + else + plan_context = CurrentMemoryContext; /* * Create and fill the CachedPlan struct within the new context. @@ -826,6 +937,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, plan->saved_xmin = InvalidTransactionId; plan->refcount = 0; plan->context = plan_context; + plan->is_oneshot = plansource->is_oneshot; plan->is_saved = false; plan->is_valid = true; @@ -847,7 +959,11 @@ choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams) { double avg_custom_cost; - /* Never any point in a custom plan if there's no parameters */ + /* One-shot plans will always be considered custom */ + if (plansource->is_oneshot) + return true; + + /* Otherwise, never any point in a custom plan if there's no parameters */ if (boundParams == NULL) return false; @@ -1049,7 +1165,14 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) Assert(plan->refcount > 0); plan->refcount--; if (plan->refcount == 0) - MemoryContextDelete(plan->context); + { + /* Mark it no longer valid */ + plan->magic = 0; + + /* One-shot plans do not own their context, so we can't free them */ + if (!plan->is_oneshot) + MemoryContextDelete(plan->context); + } } /* @@ -1066,9 +1189,11 @@ CachedPlanSetParentContext(CachedPlanSource *plansource, Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plansource->is_complete); - /* This seems worth a real test, though */ + /* These seem worth real tests, though */ if (plansource->is_saved) elog(ERROR, "cannot move a saved cached plan to another context"); + if (plansource->is_oneshot) + elog(ERROR, "cannot move a one-shot cached plan to another context"); /* OK, let the caller keep the plan where he wishes */ MemoryContextSetParent(plansource->context, newcontext); @@ -1105,6 +1230,13 @@ CopyCachedPlan(CachedPlanSource *plansource) Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plansource->is_complete); + /* + * One-shot plans can't be copied, because we haven't taken care that + * parsing/planning didn't scribble on the raw parse tree or querytrees. + */ + if (plansource->is_oneshot) + elog(ERROR, "cannot copy a one-shot cached plan"); + source_context = AllocSetContextCreate(CurrentMemoryContext, "CachedPlanSource", ALLOCSET_SMALL_MINSIZE, @@ -1152,6 +1284,7 @@ CopyCachedPlan(CachedPlanSource *plansource) newsource->gplan = NULL; + newsource->is_oneshot = false; newsource->is_complete = true; newsource->is_saved = false; newsource->is_valid = plansource->is_valid; |