aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/cache/plancache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/cache/plancache.c')
-rw-r--r--src/backend/utils/cache/plancache.c1521
1 files changed, 998 insertions, 523 deletions
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 68b6783e196..62fdf2877db 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -3,19 +3,23 @@
* plancache.c
* Plan cache management.
*
- * We can store a cached plan in either fully-planned format, or just
- * parsed-and-rewritten if the caller wishes to postpone planning until
- * actual parameter values are available. CachedPlanSource has the same
- * contents either way, but CachedPlan contains a list of PlannedStmts
- * and bare utility statements in the first case, or a list of Query nodes
- * in the second case.
+ * The plan cache manager has two principal responsibilities: deciding when
+ * to use a generic plan versus a custom (parameter-value-specific) plan,
+ * and tracking whether cached plans need to be invalidated because of schema
+ * changes in the objects they depend on.
*
- * The plan cache manager itself is principally responsible for tracking
- * whether cached plans should be invalidated because of schema changes in
- * the objects they depend on. When (and if) the next demand for a cached
- * plan occurs, the query will be replanned. Note that this could result
- * in an error, for example if a column referenced by the query is no
- * longer present. The creator of a cached plan can specify whether it
+ * The logic for choosing generic or custom plans is in choose_custom_plan,
+ * which see for comments.
+ *
+ * Cache invalidation is driven off sinval events. Any CachedPlanSource
+ * that matches the event is marked invalid, as is its generic CachedPlan
+ * if it has one. When (and if) the next demand for a cached plan occurs,
+ * parse analysis and rewrite is repeated to build a new valid query tree,
+ * and then planning is performed as normal.
+ *
+ * Note that if the sinval was a result of user DDL actions, parse analysis
+ * could throw an error, for example if a column referenced by the query is
+ * no longer present. The creator of a cached plan can specify whether it
* is allowable for the query to change output tupdesc on replan (this
* could happen with "SELECT *" for example) --- if so, it's up to the
* caller to notice changes and cope with them.
@@ -41,6 +45,8 @@
*/
#include "postgres.h"
+#include <limits.h>
+
#include "access/transam.h"
#include "catalog/namespace.h"
#include "executor/executor.h"
@@ -58,15 +64,28 @@
#include "utils/syscache.h"
-static List *cached_plans_list = NIL;
-
-static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list,
- MemoryContext plan_context);
+/*
+ * This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
+ * those that are in long-lived storage and are examined for sinval events).
+ * We thread the structs manually instead of using List cells so that we can
+ * guarantee to save a CachedPlanSource without error.
+ */
+static CachedPlanSource *first_saved_plan = NULL;
+
+static void ReleaseGenericPlan(CachedPlanSource *plansource);
+static List *RevalidateCachedQuery(CachedPlanSource *plansource);
+static bool CheckCachedPlan(CachedPlanSource *plansource);
+static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
+ ParamListInfo boundParams);
+static bool choose_custom_plan(CachedPlanSource *plansource,
+ ParamListInfo boundParams);
+static double cached_plan_cost(CachedPlan *plan);
static void AcquireExecutorLocks(List *stmt_list, bool acquire);
static void AcquirePlannerLocks(List *stmt_list, bool acquire);
static void ScanQueryForLocks(Query *parsetree, bool acquire);
static bool ScanQueryWalker(Node *node, bool *acquire);
static bool plan_list_is_transient(List *stmt_list);
+static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
static void PlanCacheRelCallback(Datum arg, Oid relid);
static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -90,32 +109,33 @@ InitPlanCache(void)
/*
* CreateCachedPlan: initially create a plan cache entry.
*
- * The caller must already have successfully parsed/planned the query;
- * about all that we do here is copy it into permanent storage.
+ * Creation of a cached plan is divided into two steps, CreateCachedPlan and
+ * CompleteCachedPlan. CreateCachedPlan should be called after running the
+ * query through raw_parser, but before doing parse analysis and rewrite;
+ * CompleteCachedPlan is called after that. The reason for this arrangement
+ * is that it can save one round of copying of the raw parse tree, since
+ * the parser will normally scribble on the raw parse tree. Callers would
+ * otherwise need to make an extra copy of the parse tree to ensure they
+ * still had a clean copy to present at plan cache creation time.
+ *
+ * All arguments presented to CreateCachedPlan are copied into a memory
+ * context created as a child of the call-time CurrentMemoryContext, which
+ * should be a reasonably short-lived working context that will go away in
+ * event of an error. This ensures that the cached plan data structure will
+ * likewise disappear if an error occurs before we have fully constructed it.
+ * Once constructed, the cached plan can be made longer-lived, if needed,
+ * by calling SaveCachedPlan.
*
* raw_parse_tree: output of raw_parser()
- * query_string: original query text (as of PG 8.4, must not be NULL)
+ * query_string: original query text
* commandTag: compile-time-constant tag for query, or NULL if empty query
- * param_types: array of fixed parameter type OIDs, or NULL if none
- * num_params: number of fixed parameters
- * cursor_options: options bitmask that was/will be passed to planner
- * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees
- * fully_planned: are we caching planner or rewriter output?
- * fixed_result: TRUE to disallow changes in result tupdesc
*/
CachedPlanSource *
CreateCachedPlan(Node *raw_parse_tree,
const char *query_string,
- const char *commandTag,
- Oid *param_types,
- int num_params,
- int cursor_options,
- List *stmt_list,
- bool fully_planned,
- bool fixed_result)
+ const char *commandTag)
{
CachedPlanSource *plansource;
- OverrideSearchPath *search_path;
MemoryContext source_context;
MemoryContext oldcxt;
@@ -123,61 +143,50 @@ CreateCachedPlan(Node *raw_parse_tree,
/*
* Make a dedicated memory context for the CachedPlanSource and its
- * subsidiary data. We expect it can be pretty small.
+ * permanent subsidiary data. It's probably not going to be large, but
+ * just in case, use the default maxsize parameter. Initially it's a
+ * child of the caller's context (which we assume to be transient), so
+ * that it will be cleaned up on error.
*/
- source_context = AllocSetContextCreate(CacheMemoryContext,
+ source_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlanSource",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
- ALLOCSET_SMALL_MAXSIZE);
-
- /*
- * Fetch current search_path into new context, but do any recalculation
- * work required in caller's context.
- */
- search_path = GetOverrideSearchPath(source_context);
+ ALLOCSET_DEFAULT_MAXSIZE);
/*
* Create and fill the CachedPlanSource struct within the new context.
+ * Most fields are just left empty for the moment.
*/
oldcxt = MemoryContextSwitchTo(source_context);
- plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
+
+ plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
+ plansource->magic = CACHEDPLANSOURCE_MAGIC;
plansource->raw_parse_tree = copyObject(raw_parse_tree);
plansource->query_string = pstrdup(query_string);
- plansource->commandTag = commandTag; /* no copying needed */
- if (num_params > 0)
- {
- plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid));
- memcpy(plansource->param_types, param_types, num_params * sizeof(Oid));
- }
- else
- plansource->param_types = NULL;
- plansource->num_params = num_params;
- /* these can be set later with CachedPlanSetParserHook: */
+ plansource->commandTag = commandTag;
+ plansource->param_types = NULL;
+ plansource->num_params = 0;
plansource->parserSetup = NULL;
plansource->parserSetupArg = NULL;
- plansource->cursor_options = cursor_options;
- plansource->fully_planned = fully_planned;
- plansource->fixed_result = fixed_result;
- plansource->search_path = search_path;
- plansource->generation = 0; /* StoreCachedPlan will increment */
- plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
- plansource->plan = NULL;
+ plansource->cursor_options = 0;
+ plansource->fixed_result = false;
+ plansource->resultDesc = NULL;
+ plansource->search_path = NULL;
plansource->context = source_context;
- plansource->orig_plan = NULL;
-
- /*
- * Copy the current output plans into the plancache entry.
- */
- StoreCachedPlan(plansource, stmt_list, NULL);
-
- /*
- * Now we can add the entry to the list of cached plans. The List nodes
- * live in CacheMemoryContext.
- */
- MemoryContextSwitchTo(CacheMemoryContext);
-
- cached_plans_list = lappend(cached_plans_list, plansource);
+ plansource->query_list = NIL;
+ plansource->relationOids = NIL;
+ plansource->invalItems = NIL;
+ plansource->query_context = NULL;
+ plansource->gplan = NULL;
+ 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;
MemoryContextSwitchTo(oldcxt);
@@ -185,274 +194,465 @@ CreateCachedPlan(Node *raw_parse_tree,
}
/*
- * FastCreateCachedPlan: create a plan cache entry with minimal data copying.
+ * CompleteCachedPlan: second step of creating a plan cache entry.
+ *
+ * Pass in the analyzed-and-rewritten form of the query, as well as the
+ * required subsidiary data about parameters and such. All passed values will
+ * be copied into the CachedPlanSource's memory, except as specified below.
+ * After this is called, GetCachedPlan can be called to obtain a plan, and
+ * optionally the CachedPlanSource can be saved using SaveCachedPlan.
+ *
+ * If querytree_context is not NULL, the querytree_list must be stored in that
+ * context (but the other parameters need not be). The querytree_list is not
+ * copied, rather the given context is kept as the initial query_context of
+ * the CachedPlanSource. (It should have been created as a child of the
+ * caller's working memory context, but it will now be reparented to belong
+ * to the CachedPlanSource.) The querytree_context is normally the context in
+ * which the caller did raw parsing and parse analysis. This approach saves
+ * one tree copying step compared to passing NULL, but leaves lots of extra
+ * cruft in the query_context, namely whatever extraneous stuff parse analysis
+ * created, as well as whatever went unused from the raw parse tree. Using
+ * this option is a space-for-time tradeoff that is appropriate if the
+ * CachedPlanSource is not expected to survive long.
*
- * For plans that aren't expected to live very long, the copying overhead of
- * CreateCachedPlan is annoying. We provide this variant entry point in which
- * the caller has already placed all the data in a suitable memory context.
- * The source data and completed plan are in the same context, since this
- * avoids extra copy steps during plan construction. If the query ever does
- * need replanning, we'll generate a separate new CachedPlan at that time, but
- * the CachedPlanSource and the initial CachedPlan share the caller-provided
- * context and go away together when neither is needed any longer. (Because
- * the parser and planner generate extra cruft in addition to their real
- * output, this approach means that the context probably contains a bunch of
- * useless junk as well as the useful trees. Hence, this method is a
- * space-for-time tradeoff, which is worth making for plans expected to be
- * short-lived.)
+ * plancache.c cannot know how to copy the data referenced by parserSetupArg,
+ * and it would often be inappropriate to do so anyway. When using that
+ * option, it is caller's responsibility that the referenced data remains
+ * valid for as long as the CachedPlanSource exists.
*
- * raw_parse_tree, query_string, param_types, and stmt_list must reside in the
- * given context, which must have adequate lifespan (recommendation: make it a
- * child of CacheMemoryContext). Otherwise the API is the same as
- * CreateCachedPlan.
+ * plansource: structure returned by CreateCachedPlan
+ * querytree_list: analyzed-and-rewritten form of query (list of Query nodes)
+ * querytree_context: memory context containing querytree_list,
+ * or NULL to copy querytree_list into a fresh context
+ * param_types: array of fixed parameter type OIDs, or NULL if none
+ * num_params: number of fixed parameters
+ * parserSetup: alternate method for handling query parameters
+ * parserSetupArg: data to pass to parserSetup
+ * cursor_options: options bitmask to pass to planner
+ * fixed_result: TRUE to disallow future changes in query's result tupdesc
*/
-CachedPlanSource *
-FastCreateCachedPlan(Node *raw_parse_tree,
- char *query_string,
- const char *commandTag,
- Oid *param_types,
- int num_params,
- int cursor_options,
- List *stmt_list,
- bool fully_planned,
- bool fixed_result,
- MemoryContext context)
+void
+CompleteCachedPlan(CachedPlanSource *plansource,
+ List *querytree_list,
+ MemoryContext querytree_context,
+ Oid *param_types,
+ int num_params,
+ ParserSetupHook parserSetup,
+ void *parserSetupArg,
+ int cursor_options,
+ bool fixed_result)
{
- CachedPlanSource *plansource;
- OverrideSearchPath *search_path;
- MemoryContext oldcxt;
+ MemoryContext source_context = plansource->context;
+ MemoryContext oldcxt = CurrentMemoryContext;
- Assert(query_string != NULL); /* required as of 8.4 */
+ /* Assert caller is doing things in a sane order */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(!plansource->is_complete);
+
+ /*
+ * 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.
+ */
+ if (querytree_context != NULL)
+ {
+ MemoryContextSetParent(querytree_context, source_context);
+ MemoryContextSwitchTo(querytree_context);
+ }
+ else
+ {
+ /* Again, it's a good bet the querytree_context can be small */
+ querytree_context = AllocSetContextCreate(source_context,
+ "CachedPlanQuery",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ MemoryContextSwitchTo(querytree_context);
+ querytree_list = (List *) copyObject(querytree_list);
+ }
+
+ plansource->query_context = querytree_context;
+ plansource->query_list = querytree_list;
/*
- * Fetch current search_path into given context, but do any recalculation
- * work required in caller's context.
+ * 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.)
*/
- search_path = GetOverrideSearchPath(context);
+ extract_query_dependencies((Node *) querytree_list,
+ &plansource->relationOids,
+ &plansource->invalItems);
/*
- * Create and fill the CachedPlanSource struct within the given context.
+ * Save the final parameter types (or other parameter specification data)
+ * into the source_context, as well as our other parameters. Also save
+ * the result tuple descriptor.
*/
- oldcxt = MemoryContextSwitchTo(context);
- plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
- plansource->raw_parse_tree = raw_parse_tree;
- plansource->query_string = query_string;
- plansource->commandTag = commandTag; /* no copying needed */
- plansource->param_types = param_types;
+ MemoryContextSwitchTo(source_context);
+
+ if (num_params > 0)
+ {
+ plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid));
+ memcpy(plansource->param_types, param_types, num_params * sizeof(Oid));
+ }
+ else
+ plansource->param_types = NULL;
plansource->num_params = num_params;
- /* these can be set later with CachedPlanSetParserHook: */
- plansource->parserSetup = NULL;
- plansource->parserSetupArg = NULL;
+ plansource->parserSetup = parserSetup;
+ plansource->parserSetupArg = parserSetupArg;
plansource->cursor_options = cursor_options;
- plansource->fully_planned = fully_planned;
plansource->fixed_result = fixed_result;
- plansource->search_path = search_path;
- plansource->generation = 0; /* StoreCachedPlan will increment */
- plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
- plansource->plan = NULL;
- plansource->context = context;
- plansource->orig_plan = NULL;
+ plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list);
+
+ MemoryContextSwitchTo(oldcxt);
/*
- * Store the current output plans into the plancache entry.
+ * Fetch current search_path into dedicated context, but do any
+ * recalculation work required in caller's context.
*/
- StoreCachedPlan(plansource, stmt_list, context);
+ plansource->search_path = GetOverrideSearchPath(source_context);
+
+ plansource->is_complete = true;
+ plansource->is_valid = true;
+}
+
+/*
+ * SaveCachedPlan: save a cached plan permanently
+ *
+ * This function moves the cached plan underneath CacheMemoryContext (making
+ * it live for the life of the backend, unless explicitly dropped), and adds
+ * 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
+ * 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
+ * automatically on transaction abort.
+ */
+void
+SaveCachedPlan(CachedPlanSource *plansource)
+{
+ /* Assert caller is doing things in a sane order */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plansource->is_complete);
+ Assert(!plansource->is_saved);
/*
- * Since the context is owned by the CachedPlan, advance its refcount.
+ * In typical use, this function would be called before generating any
+ * plans from the CachedPlanSource. If there is a generic plan, moving
+ * it into CacheMemoryContext would be pretty risky since it's unclear
+ * whether the caller has taken suitable care with making references
+ * long-lived. Best thing to do seems to be to discard the plan.
*/
- plansource->orig_plan = plansource->plan;
- plansource->orig_plan->refcount++;
+ ReleaseGenericPlan(plansource);
/*
- * Now we can add the entry to the list of cached plans. The List nodes
- * live in CacheMemoryContext.
+ * Reparent the source memory context under CacheMemoryContext so that
+ * it will live indefinitely. The query_context follows along since it's
+ * already a child of the other one.
*/
- MemoryContextSwitchTo(CacheMemoryContext);
-
- cached_plans_list = lappend(cached_plans_list, plansource);
+ MemoryContextSetParent(plansource->context, CacheMemoryContext);
- MemoryContextSwitchTo(oldcxt);
+ /*
+ * Add the entry to the global list of cached plans.
+ */
+ plansource->next_saved = first_saved_plan;
+ first_saved_plan = plansource;
- return plansource;
+ plansource->is_saved = true;
}
/*
- * CachedPlanSetParserHook: set up to use parser callback hooks
+ * DropCachedPlan: destroy a cached plan.
*
- * Use this when a caller wants to manage parameter information via parser
- * callbacks rather than a fixed parameter-types list. Beware that the
- * information pointed to by parserSetupArg must be valid for as long as
- * the cached plan might be replanned!
+ * Actually this only destroys the CachedPlanSource: any referenced CachedPlan
+ * is released, but not destroyed until its refcount goes to zero. That
+ * handles the situation where DropCachedPlan is called while the plan is
+ * still in use.
*/
void
-CachedPlanSetParserHook(CachedPlanSource *plansource,
- ParserSetupHook parserSetup,
- void *parserSetupArg)
+DropCachedPlan(CachedPlanSource *plansource)
{
- /* Must not have specified a fixed parameter-types list */
- Assert(plansource->param_types == NULL);
- Assert(plansource->num_params == 0);
- /* OK, save hook info */
- plansource->parserSetup = parserSetup;
- plansource->parserSetupArg = parserSetupArg;
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+
+ /* If it's been saved, remove it from the list */
+ if (plansource->is_saved)
+ {
+ if (first_saved_plan == plansource)
+ first_saved_plan = plansource->next_saved;
+ else
+ {
+ CachedPlanSource *psrc;
+
+ for (psrc = first_saved_plan; psrc; psrc = psrc->next_saved)
+ {
+ if (psrc->next_saved == plansource)
+ {
+ psrc->next_saved = plansource->next_saved;
+ break;
+ }
+ }
+ }
+ plansource->is_saved = false;
+ }
+
+ /* Decrement generic CachePlan's refcount and drop if no longer needed */
+ ReleaseGenericPlan(plansource);
+
+ /*
+ * Remove the CachedPlanSource and all subsidiary data (including the
+ * query_context if any).
+ */
+ MemoryContextDelete(plansource->context);
}
/*
- * StoreCachedPlan: store a built or rebuilt plan into a plancache entry.
- *
- * Common subroutine for CreateCachedPlan and RevalidateCachedPlan.
+ * ReleaseGenericPlan: release a CachedPlanSource's generic plan, if any.
*/
static void
-StoreCachedPlan(CachedPlanSource *plansource,
- List *stmt_list,
- MemoryContext plan_context)
+ReleaseGenericPlan(CachedPlanSource *plansource)
{
- CachedPlan *plan;
+ /* Be paranoid about the possibility that ReleaseCachedPlan fails */
+ if (plansource->gplan)
+ {
+ CachedPlan *plan = plansource->gplan;
+
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
+ plansource->gplan = NULL;
+ ReleaseCachedPlan(plan, false);
+ }
+}
+
+/*
+ * RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree.
+ *
+ * What we do here is re-acquire locks and redo parse analysis if necessary.
+ * On return, the query_list is valid and we have sufficient locks to begin
+ * planning.
+ *
+ * If any parse analysis activity is required, the caller's memory context is
+ * used for that work.
+ *
+ * The result value is the transient analyzed-and-rewritten query tree if we
+ * had to do re-analysis, and NIL otherwise. (This is returned just to save
+ * a tree copying step in a subsequent BuildCachedPlan call.)
+ */
+static List *
+RevalidateCachedQuery(CachedPlanSource *plansource)
+{
+ bool snapshot_set;
+ Node *rawtree;
+ List *tlist; /* transient query-tree list */
+ List *qlist; /* permanent query-tree list */
+ TupleDesc resultDesc;
+ MemoryContext querytree_context;
MemoryContext oldcxt;
- if (plan_context == NULL)
+ /*
+ * 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.
+ */
+ if (plansource->is_valid)
{
- /*
- * 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.
- */
- plan_context = AllocSetContextCreate(CacheMemoryContext,
- "CachedPlan",
- ALLOCSET_SMALL_MINSIZE,
- ALLOCSET_SMALL_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
+ AcquirePlannerLocks(plansource->query_list, true);
/*
- * Copy supplied data into the new context.
+ * By now, if any invalidation has happened, the inval callback
+ * functions will have marked the query invalid.
*/
- oldcxt = MemoryContextSwitchTo(plan_context);
+ if (plansource->is_valid)
+ {
+ /* Successfully revalidated and locked the query. */
+ return NIL;
+ }
- stmt_list = (List *) copyObject(stmt_list);
+ /* Ooops, the race case happened. Release useless locks. */
+ AcquirePlannerLocks(plansource->query_list, false);
}
- else
+
+ /*
+ * Discard the no-longer-useful query tree. (Note: we don't want to
+ * do this any earlier, else we'd not have been able to release locks
+ * correctly in the race condition case.)
+ */
+ plansource->is_valid = false;
+ plansource->query_list = NIL;
+ plansource->relationOids = NIL;
+ plansource->invalItems = NIL;
+
+ /*
+ * Free the query_context. We don't really expect MemoryContextDelete to
+ * fail, but just in case, make sure the CachedPlanSource is left in a
+ * reasonably sane state. (The generic plan won't get unlinked yet,
+ * but that's acceptable.)
+ */
+ if (plansource->query_context)
{
- /* Assume subsidiary data is in the given context */
- oldcxt = MemoryContextSwitchTo(plan_context);
+ MemoryContext qcxt = plansource->query_context;
+
+ plansource->query_context = NULL;
+ MemoryContextDelete(qcxt);
}
+ /* Drop the generic plan reference if any */
+ ReleaseGenericPlan(plansource);
+
/*
- * Create and fill the CachedPlan struct within the new context.
+ * Now re-do parse analysis and rewrite. This not incidentally acquires
+ * the locks we need to do planning safely.
*/
- plan = (CachedPlan *) palloc(sizeof(CachedPlan));
- plan->stmt_list = stmt_list;
- plan->fully_planned = plansource->fully_planned;
- plan->dead = false;
- if (plansource->fully_planned && plan_list_is_transient(stmt_list))
+ Assert(plansource->is_complete);
+
+ /*
+ * Restore the search_path that was in use when the plan was made. See
+ * comments for PushOverrideSearchPath about limitations of this.
+ *
+ * (XXX is there anything else we really need to restore?)
+ */
+ PushOverrideSearchPath(plansource->search_path);
+
+ /*
+ * If a snapshot is already set (the normal case), we can just use that
+ * for parsing/planning. But if it isn't, install one. Note: no point in
+ * checking whether parse analysis requires a snapshot; utility commands
+ * don't have invalidatable plans, so we'd not get here for such a
+ * command.
+ */
+ snapshot_set = false;
+ if (!ActiveSnapshotSet())
{
- Assert(TransactionIdIsNormal(TransactionXmin));
- plan->saved_xmin = TransactionXmin;
+ PushActiveSnapshot(GetTransactionSnapshot());
+ snapshot_set = true;
}
+
+ /*
+ * Run parse analysis and rule rewriting. The parser tends to scribble on
+ * its input, so we must copy the raw parse tree to prevent corruption of
+ * the cache.
+ */
+ rawtree = copyObject(plansource->raw_parse_tree);
+ if (plansource->parserSetup != NULL)
+ tlist = pg_analyze_and_rewrite_params(rawtree,
+ plansource->query_string,
+ plansource->parserSetup,
+ plansource->parserSetupArg);
else
- plan->saved_xmin = InvalidTransactionId;
- plan->refcount = 1; /* for the parent's link */
- plan->generation = ++(plansource->generation);
- plan->context = plan_context;
- if (plansource->fully_planned)
- {
- /*
- * Planner already extracted dependencies, we don't have to ... except
- * in the case of EXPLAIN. We assume here that EXPLAIN can't appear
- * in a list with other commands.
- */
- plan->relationOids = plan->invalItems = NIL;
+ tlist = pg_analyze_and_rewrite(rawtree,
+ plansource->query_string,
+ plansource->param_types,
+ plansource->num_params);
- if (list_length(stmt_list) == 1 &&
- IsA(linitial(stmt_list), ExplainStmt))
- {
- ExplainStmt *estmt = (ExplainStmt *) linitial(stmt_list);
+ /* Release snapshot if we got one */
+ if (snapshot_set)
+ PopActiveSnapshot();
- extract_query_dependencies(estmt->query,
- &plan->relationOids,
- &plan->invalItems);
- }
+ /* Now we can restore current search path */
+ PopOverrideSearchPath();
+
+ /*
+ * Check or update the result tupdesc. XXX should we use a weaker
+ * condition than equalTupleDescs() here?
+ *
+ * We assume the parameter types didn't change from the first time, so no
+ * need to update that.
+ */
+ resultDesc = PlanCacheComputeResultDesc(tlist);
+ if (resultDesc == NULL && plansource->resultDesc == NULL)
+ {
+ /* OK, doesn't return tuples */
}
- else
+ else if (resultDesc == NULL || plansource->resultDesc == NULL ||
+ !equalTupleDescs(resultDesc, plansource->resultDesc))
{
- /* Use the planner machinery to extract dependencies */
- extract_query_dependencies((Node *) stmt_list,
- &plan->relationOids,
- &plan->invalItems);
+ /* can we give a better error message? */
+ if (plansource->fixed_result)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cached plan must not change result type")));
+ oldcxt = MemoryContextSwitchTo(plansource->context);
+ if (resultDesc)
+ resultDesc = CreateTupleDescCopy(resultDesc);
+ if (plansource->resultDesc)
+ FreeTupleDesc(plansource->resultDesc);
+ plansource->resultDesc = resultDesc;
+ MemoryContextSwitchTo(oldcxt);
}
- Assert(plansource->plan == NULL);
- plansource->plan = plan;
+ /*
+ * Allocate new query_context and copy the completed querytree into it.
+ * It's transient until we complete the copying and dependency extraction.
+ */
+ querytree_context = AllocSetContextCreate(CurrentMemoryContext,
+ "CachedPlanQuery",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ oldcxt = MemoryContextSwitchTo(querytree_context);
- MemoryContextSwitchTo(oldcxt);
-}
+ qlist = (List *) copyObject(tlist);
-/*
- * DropCachedPlan: destroy a cached plan.
- *
- * Actually this only destroys the CachedPlanSource: the referenced CachedPlan
- * is released, but not destroyed until its refcount goes to zero. That
- * handles the situation where DropCachedPlan is called while the plan is
- * still in use.
- */
-void
-DropCachedPlan(CachedPlanSource *plansource)
-{
- /* Validity check that we were given a CachedPlanSource */
- Assert(list_member_ptr(cached_plans_list, 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.)
+ */
+ extract_query_dependencies((Node *) qlist,
+ &plansource->relationOids,
+ &plansource->invalItems);
- /* Remove it from the list */
- cached_plans_list = list_delete_ptr(cached_plans_list, plansource);
+ MemoryContextSwitchTo(oldcxt);
+
+ /* Now reparent the finished query_context and save the links */
+ MemoryContextSetParent(querytree_context, plansource->context);
- /* Decrement child CachePlan's refcount and drop if no longer needed */
- if (plansource->plan)
- ReleaseCachedPlan(plansource->plan, false);
+ plansource->query_context = querytree_context;
+ plansource->query_list = qlist;
/*
- * If CachedPlanSource has independent storage, just drop it. Otherwise
- * decrement the refcount on the CachePlan that owns the storage.
+ * Note: we do not reset generic_cost or total_custom_cost, although
+ * we could choose to do so. If the DDL or statistics change that
+ * prompted the invalidation meant a significant change in the cost
+ * estimates, it would be better to reset those variables and start
+ * fresh; but often it doesn't, and we're better retaining our hard-won
+ * knowledge about the relative costs.
*/
- if (plansource->orig_plan == NULL)
- {
- /* Remove the CachedPlanSource and all subsidiary data */
- MemoryContextDelete(plansource->context);
- }
- else
- {
- Assert(plansource->context == plansource->orig_plan->context);
- ReleaseCachedPlan(plansource->orig_plan, false);
- }
+
+ plansource->is_valid = true;
+
+ /* Return transient copy of querytrees for possible use in planning */
+ return tlist;
}
/*
- * RevalidateCachedPlan: prepare for re-use of a previously cached plan.
- *
- * What we do here is re-acquire locks and rebuild the plan if necessary.
- * On return, the plan is valid and we have sufficient locks to begin
- * execution (or planning, if not fully_planned).
+ * CheckCachedPlan: see if the CachedPlanSource's generic plan is valid.
*
- * 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.
+ * Caller must have already called RevalidateCachedQuery to verify that the
+ * querytree is up to date.
*
- * Note: if any replanning activity is required, the caller's memory context
- * is used for that work.
+ * On a "true" return, we have acquired the locks needed to run the plan.
+ * (We must do this for the "true" result to be race-condition-free.)
*/
-CachedPlan *
-RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
+static bool
+CheckCachedPlan(CachedPlanSource *plansource)
{
- CachedPlan *plan;
+ CachedPlan *plan = plansource->gplan;
+
+ /* Assert that caller checked the querytree */
+ Assert(plansource->is_valid);
- /* Validity check that we were given a CachedPlanSource */
- Assert(list_member_ptr(cached_plans_list, plansource));
+ /* If there's no generic plan, just say "false" */
+ if (!plan)
+ return false;
+
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
/*
- * If the plan currently appears 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 lock.
+ * If it appears valid, acquire locks and recheck; this is much the same
+ * logic as in RevalidateCachedQuery, but for a plan.
*/
- plan = plansource->plan;
- if (plan && !plan->dead)
+ if (plan->is_valid)
{
/*
* Plan must have positive refcount because it is referenced by
@@ -460,164 +660,348 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
*/
Assert(plan->refcount > 0);
- if (plan->fully_planned)
- AcquireExecutorLocks(plan->stmt_list, true);
- else
- AcquirePlannerLocks(plan->stmt_list, true);
+ AcquireExecutorLocks(plan->stmt_list, true);
/*
* If plan was transient, check to see if TransactionXmin has
* advanced, and if so invalidate it.
*/
- if (!plan->dead &&
+ if (plan->is_valid &&
TransactionIdIsValid(plan->saved_xmin) &&
!TransactionIdEquals(plan->saved_xmin, TransactionXmin))
- plan->dead = true;
+ plan->is_valid = false;
/*
* By now, if any invalidation has happened, the inval callback
- * functions will have marked the plan dead.
+ * functions will have marked the plan invalid.
*/
- if (plan->dead)
+ if (plan->is_valid)
{
- /* Ooops, the race case happened. Release useless locks. */
- if (plan->fully_planned)
- AcquireExecutorLocks(plan->stmt_list, false);
- else
- AcquirePlannerLocks(plan->stmt_list, false);
+ /* Successfully revalidated and locked the query. */
+ return true;
}
+
+ /* Ooops, the race case happened. Release useless locks. */
+ AcquireExecutorLocks(plan->stmt_list, false);
}
/*
- * If plan has been invalidated, unlink it from the parent and release it.
+ * Plan has been invalidated, so unlink it from the parent and release it.
*/
- if (plan && plan->dead)
+ ReleaseGenericPlan(plansource);
+
+ return false;
+}
+
+/*
+ * BuildCachedPlan: construct a new CachedPlan from a CachedPlanSource.
+ *
+ * qlist should be the result value from a previous RevalidateCachedQuery.
+ *
+ * To build a generic, parameter-value-independent plan, pass NULL for
+ * boundParams. To build a custom plan, pass the actual parameter values via
+ * boundParams. For best effect, the PARAM_FLAG_CONST flag should be set on
+ * each parameter value; otherwise the planner will treat the value as a
+ * 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.
+ */
+static CachedPlan *
+BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
+ ParamListInfo boundParams)
+{
+ CachedPlan *plan;
+ List *plist;
+ bool snapshot_set;
+ bool spi_pushed;
+ MemoryContext plan_context;
+ MemoryContext oldcxt;
+
+ /* Assert that caller checked the querytree */
+ Assert(plansource->is_valid);
+
+ /*
+ * If we don't already have a copy of the querytree list that can be
+ * scribbled on by the planner, make one.
+ */
+ if (qlist == NIL)
+ qlist = (List *) copyObject(plansource->query_list);
+
+ /*
+ * Restore the search_path that was in use when the plan was made. See
+ * comments for PushOverrideSearchPath about limitations of this.
+ *
+ * (XXX is there anything else we really need to restore?)
+ *
+ * Note: it's a bit annoying to do this and snapshot-setting twice in the
+ * case where we have to do both re-analysis and re-planning. However,
+ * until there's some evidence that the cost is actually meaningful
+ * compared to parse analysis + planning, I'm not going to contort the
+ * code enough to avoid that.
+ */
+ PushOverrideSearchPath(plansource->search_path);
+
+ /*
+ * If a snapshot is already set (the normal case), we can just use
+ * that for parsing/planning. But if it isn't, install one. Note: no
+ * point in checking whether parse analysis requires a snapshot;
+ * utility commands don't have invalidatable plans, so we'd not get
+ * here for such a command.
+ */
+ snapshot_set = false;
+ if (!ActiveSnapshotSet())
{
- plansource->plan = NULL;
- ReleaseCachedPlan(plan, false);
- plan = NULL;
+ PushActiveSnapshot(GetTransactionSnapshot());
+ snapshot_set = true;
}
/*
- * Build a new plan if needed.
+ * The planner may try to call SPI-using functions, which causes a
+ * problem if we're already inside one. Rather than expect all
+ * SPI-using code to do SPI_push whenever a replan could happen,
+ * it seems best to take care of the case here.
*/
- if (!plan)
+ spi_pushed = SPI_push_conditional();
+
+ /*
+ * Generate the plan.
+ */
+ plist = pg_plan_queries(qlist, plansource->cursor_options, boundParams);
+
+ /* Clean up SPI state */
+ SPI_pop_conditional(spi_pushed);
+
+ /* Release snapshot if we got one */
+ if (snapshot_set)
+ PopActiveSnapshot();
+
+ /* Now we can restore current search path */
+ 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.
+ */
+ plan_context = AllocSetContextCreate(CurrentMemoryContext,
+ "CachedPlan",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ /*
+ * Copy plan into the new context.
+ */
+ oldcxt = MemoryContextSwitchTo(plan_context);
+
+ plist = (List *) copyObject(plist);
+
+ /*
+ * Create and fill the CachedPlan struct within the new context.
+ */
+ plan = (CachedPlan *) palloc(sizeof(CachedPlan));
+ plan->magic = CACHEDPLAN_MAGIC;
+ plan->stmt_list = plist;
+ if (plan_list_is_transient(plist))
{
- bool snapshot_set = false;
- Node *rawtree;
- List *slist;
- TupleDesc resultDesc;
+ Assert(TransactionIdIsNormal(TransactionXmin));
+ plan->saved_xmin = TransactionXmin;
+ }
+ else
+ plan->saved_xmin = InvalidTransactionId;
+ plan->refcount = 0;
+ plan->context = plan_context;
+ plan->is_saved = false;
+ plan->is_valid = true;
- /*
- * Restore the search_path that was in use when the plan was made. See
- * comments for PushOverrideSearchPath about limitations of this.
- *
- * (XXX is there anything else we really need to restore?)
- */
- PushOverrideSearchPath(plansource->search_path);
+ /* assign generation number to new plan */
+ plan->generation = ++(plansource->generation);
- /*
- * If a snapshot is already set (the normal case), we can just use
- * that for parsing/planning. But if it isn't, install one. Note: no
- * point in checking whether parse analysis requires a snapshot;
- * utility commands don't have invalidatable plans, so we'd not get
- * here for such a command.
- */
- if (!ActiveSnapshotSet())
- {
- PushActiveSnapshot(GetTransactionSnapshot());
- snapshot_set = true;
- }
+ MemoryContextSwitchTo(oldcxt);
- /*
- * Run parse analysis and rule rewriting. The parser tends to
- * scribble on its input, so we must copy the raw parse tree to
- * prevent corruption of the cache.
- */
- rawtree = copyObject(plansource->raw_parse_tree);
- if (plansource->parserSetup != NULL)
- slist = pg_analyze_and_rewrite_params(rawtree,
- plansource->query_string,
- plansource->parserSetup,
- plansource->parserSetupArg);
- else
- slist = pg_analyze_and_rewrite(rawtree,
- plansource->query_string,
- plansource->param_types,
- plansource->num_params);
+ return plan;
+}
- if (plansource->fully_planned)
- {
- /*
- * Generate plans for queries.
- *
- * The planner may try to call SPI-using functions, which causes a
- * problem if we're already inside one. Rather than expect all
- * SPI-using code to do SPI_push whenever a replan could happen,
- * it seems best to take care of the case here.
- */
- bool pushed;
+/*
+ * choose_custom_plan: choose whether to use custom or generic plan
+ *
+ * This defines the policy followed by GetCachedPlan.
+ */
+static bool
+choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
+{
+ double avg_custom_cost;
- pushed = SPI_push_conditional();
+ /* Never any point in a custom plan if there's no parameters */
+ if (boundParams == NULL)
+ return false;
- slist = pg_plan_queries(slist, plansource->cursor_options, NULL);
+ /* See if caller wants to force the decision */
+ if (plansource->cursor_options & CURSOR_OPT_GENERIC_PLAN)
+ return false;
+ if (plansource->cursor_options & CURSOR_OPT_CUSTOM_PLAN)
+ return true;
- SPI_pop_conditional(pushed);
- }
+ /* Generate custom plans until we have done at least 5 (arbitrary) */
+ if (plansource->num_custom_plans < 5)
+ return true;
- /*
- * Check or update the result tupdesc. XXX should we use a weaker
- * condition than equalTupleDescs() here?
- */
- resultDesc = PlanCacheComputeResultDesc(slist);
- if (resultDesc == NULL && plansource->resultDesc == NULL)
+ avg_custom_cost = plansource->total_custom_cost / plansource->num_custom_plans;
+
+ /*
+ * Prefer generic plan if it's less than 10% more expensive than average
+ * custom plan. This threshold is a bit arbitrary; it'd be better if we
+ * had some means of comparing planning time to the estimated runtime
+ * cost differential.
+ *
+ * Note that if generic_cost is -1 (indicating we've not yet determined
+ * the generic plan cost), we'll always prefer generic at this point.
+ */
+ if (plansource->generic_cost < avg_custom_cost * 1.1)
+ return false;
+
+ return true;
+}
+
+/*
+ * cached_plan_cost: calculate estimated cost of a plan
+ */
+static double
+cached_plan_cost(CachedPlan *plan)
+{
+ double result = 0;
+ ListCell *lc;
+
+ foreach(lc, plan->stmt_list)
+ {
+ PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
+
+ if (!IsA(plannedstmt, PlannedStmt))
+ continue; /* Ignore utility statements */
+
+ result += plannedstmt->planTree->total_cost;
+ }
+
+ return result;
+}
+
+/*
+ * GetCachedPlan: get a cached plan from a CachedPlanSource.
+ *
+ * This function hides the logic that decides whether to use a generic
+ * plan or a custom plan for the given parameters: the caller does not know
+ * which it will get.
+ *
+ * On return, the plan is valid and we have sufficient locks to begin
+ * 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).
+ *
+ * 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)
+{
+ CachedPlan *plan;
+ List *qlist;
+ bool customplan;
+
+ /* Assert caller is doing things in a sane order */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plansource->is_complete);
+ /* This seems worth a real test, though */
+ if (useResOwner && !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 */
+ qlist = RevalidateCachedQuery(plansource);
+
+ /* Decide whether to use a custom plan */
+ customplan = choose_custom_plan(plansource, boundParams);
+
+ if (!customplan)
+ {
+ if (CheckCachedPlan(plansource))
{
- /* OK, doesn't return tuples */
+ /* We want a generic plan, and we already have a valid one */
+ plan = plansource->gplan;
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
}
- else if (resultDesc == NULL || plansource->resultDesc == NULL ||
- !equalTupleDescs(resultDesc, plansource->resultDesc))
+ else
{
- MemoryContext oldcxt;
-
- /* can we give a better error message? */
- if (plansource->fixed_result)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cached plan must not change result type")));
- oldcxt = MemoryContextSwitchTo(plansource->context);
- if (resultDesc)
- resultDesc = CreateTupleDescCopy(resultDesc);
- if (plansource->resultDesc)
- FreeTupleDesc(plansource->resultDesc);
- plansource->resultDesc = resultDesc;
- MemoryContextSwitchTo(oldcxt);
- }
-
- /* Release snapshot if we got one */
- if (snapshot_set)
- PopActiveSnapshot();
-
- /* Now we can restore current search path */
- PopOverrideSearchPath();
+ /* Build a new generic plan */
+ plan = BuildCachedPlan(plansource, qlist, NULL);
+ /* Just make real sure plansource->gplan is clear */
+ ReleaseGenericPlan(plansource);
+ /* Link the new generic plan into the plansource */
+ plansource->gplan = plan;
+ plan->refcount++;
+ /* Immediately reparent into appropriate context */
+ if (plansource->is_saved)
+ {
+ /* saved plans all live under CacheMemoryContext */
+ MemoryContextSetParent(plan->context, CacheMemoryContext);
+ plan->is_saved = true;
+ }
+ else
+ {
+ /* otherwise, it should be a sibling of the plansource */
+ MemoryContextSetParent(plan->context,
+ MemoryContextGetParent(plansource->context));
+ }
+ /* Update generic_cost whenever we make a new generic plan */
+ plansource->generic_cost = cached_plan_cost(plan);
- /*
- * Store the plans into the plancache entry, advancing the generation
- * count.
- */
- StoreCachedPlan(plansource, slist, NULL);
+ /*
+ * If, based on the now-known value of generic_cost, we'd not have
+ * chosen to use a generic plan, then forget it and make a custom
+ * plan. This is a bit of a wart but is necessary to avoid a
+ * glitch in behavior when the custom plans are consistently big
+ * winners; at some point we'll experiment with a generic plan and
+ * find it's a loser, but we don't want to actually execute that
+ * plan.
+ */
+ customplan = choose_custom_plan(plansource, boundParams);
+ }
+ }
- plan = plansource->plan;
+ if (customplan)
+ {
+ /* Build a custom plan */
+ plan = BuildCachedPlan(plansource, qlist, boundParams);
+ /* Accumulate total costs of custom plans, but 'ware overflow */
+ if (plansource->num_custom_plans < INT_MAX)
+ {
+ plansource->total_custom_cost += cached_plan_cost(plan);
+ plansource->num_custom_plans++;
+ }
}
- /*
- * Last step: flag the plan as in use by caller.
- */
+ /* Flag the plan as in use by caller */
if (useResOwner)
ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
plan->refcount++;
if (useResOwner)
ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
+ /*
+ * Saved plans should be under CacheMemoryContext so they will not go away
+ * until their reference count goes to zero. In the generic-plan cases we
+ * already took care of that, but for a custom plan, do it as soon as we
+ * have created a reference-counted link.
+ */
+ if (customplan && plansource->is_saved)
+ {
+ MemoryContextSetParent(plan->context, CacheMemoryContext);
+ plan->is_saved = true;
+ }
+
return plan;
}
@@ -635,8 +1019,12 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
void
ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
{
+ Assert(plan->magic == CACHEDPLAN_MAGIC);
if (useResOwner)
+ {
+ Assert(plan->is_saved);
ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan);
+ }
Assert(plan->refcount > 0);
plan->refcount--;
if (plan->refcount == 0)
@@ -644,8 +1032,125 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
}
/*
- * CachedPlanIsValid: test whether the plan within a CachedPlanSource is
- * currently valid (that is, not marked as being in need of revalidation).
+ * CachedPlanSetParentContext: move a CachedPlanSource to a new memory context
+ *
+ * This can only be applied to unsaved plans; once saved, a plan always
+ * lives underneath CacheMemoryContext.
+ */
+void
+CachedPlanSetParentContext(CachedPlanSource *plansource,
+ MemoryContext newcontext)
+{
+ /* Assert caller is doing things in a sane order */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plansource->is_complete);
+
+ /* This seems worth a real test, though */
+ if (plansource->is_saved)
+ elog(ERROR, "cannot move a saved cached plan to another context");
+
+ /* OK, let the caller keep the plan where he wishes */
+ MemoryContextSetParent(plansource->context, newcontext);
+
+ /*
+ * The query_context needs no special handling, since it's a child of
+ * plansource->context. But if there's a generic plan, it should be
+ * maintained as a sibling of plansource->context.
+ */
+ if (plansource->gplan)
+ {
+ Assert(plansource->gplan->magic == CACHEDPLAN_MAGIC);
+ MemoryContextSetParent(plansource->gplan->context, newcontext);
+ }
+}
+
+/*
+ * CopyCachedPlan: make a copy of a CachedPlanSource
+ *
+ * This is a convenience routine that does the equivalent of
+ * CreateCachedPlan + CompleteCachedPlan, using the data stored in the
+ * input CachedPlanSource. The result is therefore "unsaved" (regardless
+ * of the state of the source), and we don't copy any generic plan either.
+ * The result will be currently valid, or not, the same as the source.
+ */
+CachedPlanSource *
+CopyCachedPlan(CachedPlanSource *plansource)
+{
+ CachedPlanSource *newsource;
+ MemoryContext source_context;
+ MemoryContext querytree_context;
+ MemoryContext oldcxt;
+
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plansource->is_complete);
+
+ source_context = AllocSetContextCreate(CurrentMemoryContext,
+ "CachedPlanSource",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ oldcxt = MemoryContextSwitchTo(source_context);
+
+ newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
+ newsource->magic = CACHEDPLANSOURCE_MAGIC;
+ newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
+ newsource->query_string = pstrdup(plansource->query_string);
+ newsource->commandTag = plansource->commandTag;
+ if (plansource->num_params > 0)
+ {
+ newsource->param_types = (Oid *)
+ palloc(plansource->num_params * sizeof(Oid));
+ memcpy(newsource->param_types, plansource->param_types,
+ plansource->num_params * sizeof(Oid));
+ }
+ else
+ newsource->param_types = NULL;
+ newsource->num_params = plansource->num_params;
+ newsource->parserSetup = plansource->parserSetup;
+ newsource->parserSetupArg = plansource->parserSetupArg;
+ newsource->cursor_options = plansource->cursor_options;
+ newsource->fixed_result = plansource->fixed_result;
+ if (plansource->resultDesc)
+ newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
+ else
+ newsource->resultDesc = NULL;
+ newsource->search_path = CopyOverrideSearchPath(plansource->search_path);
+ newsource->context = source_context;
+
+ querytree_context = AllocSetContextCreate(source_context,
+ "CachedPlanQuery",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ MemoryContextSwitchTo(querytree_context);
+ newsource->query_list = (List *) copyObject(plansource->query_list);
+ newsource->relationOids = (List *) copyObject(plansource->relationOids);
+ newsource->invalItems = (List *) copyObject(plansource->invalItems);
+ newsource->query_context = querytree_context;
+
+ newsource->gplan = NULL;
+
+ newsource->is_complete = true;
+ newsource->is_saved = false;
+ newsource->is_valid = plansource->is_valid;
+ newsource->generation = plansource->generation;
+ newsource->next_saved = NULL;
+
+ /* We may as well copy any acquired cost knowledge */
+ newsource->generic_cost = plansource->generic_cost;
+ newsource->total_custom_cost = plansource->total_custom_cost;
+ newsource->num_custom_plans = plansource->num_custom_plans;
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return newsource;
+}
+
+/*
+ * CachedPlanIsValid: test whether the rewritten querytree within a
+ * CachedPlanSource is currently valid (that is, not marked as being in need
+ * of revalidation).
*
* This result is only trustworthy (ie, free from race conditions) if
* the caller has acquired locks on all the relations used in the plan.
@@ -653,37 +1158,44 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
bool
CachedPlanIsValid(CachedPlanSource *plansource)
{
- CachedPlan *plan;
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ return plansource->is_valid;
+}
- /* Validity check that we were given a CachedPlanSource */
- Assert(list_member_ptr(cached_plans_list, plansource));
+/*
+ * CachedPlanGetTargetList: return tlist, if any, describing plan's output
+ *
+ * The result is guaranteed up-to-date. However, it is local storage
+ * within the cached plan, and may disappear next time the plan is updated.
+ */
+List *
+CachedPlanGetTargetList(CachedPlanSource *plansource)
+{
+ Node *pstmt;
- plan = plansource->plan;
- if (plan && !plan->dead)
- {
- /*
- * Plan must have positive refcount because it is referenced by
- * plansource; so no need to fear it disappears under us here.
- */
- Assert(plan->refcount > 0);
+ /* Assert caller is doing things in a sane order */
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
+ Assert(plansource->is_complete);
- /*
- * Although we don't want to acquire locks here, it still seems useful
- * to check for expiration of a transient plan.
- */
- if (TransactionIdIsValid(plan->saved_xmin) &&
- !TransactionIdEquals(plan->saved_xmin, TransactionXmin))
- plan->dead = true;
- else
- return true;
- }
+ /*
+ * No work needed if statement doesn't return tuples (we assume this
+ * feature cannot be changed by an invalidation)
+ */
+ if (plansource->resultDesc == NULL)
+ return NIL;
- return false;
+ /* Make sure the querytree list is valid and we have parse-time locks */
+ RevalidateCachedQuery(plansource);
+
+ /* Get the primary statement and find out what it returns */
+ pstmt = PortalListGetPrimaryStmt(plansource->query_list);
+
+ return FetchStatementTargetList(pstmt);
}
/*
- * AcquireExecutorLocks: acquire locks needed for execution of a fully-planned
- * cached plan; or release them if acquire is false.
+ * AcquireExecutorLocks: acquire locks needed for execution of a cached plan;
+ * or release them if acquire is false.
*/
static void
AcquireExecutorLocks(List *stmt_list, bool acquire)
@@ -752,8 +1264,8 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
}
/*
- * AcquirePlannerLocks: acquire locks needed for planning and execution of a
- * not-fully-planned cached plan; or release them if acquire is false.
+ * AcquirePlannerLocks: acquire locks needed for planning of a querytree list;
+ * or release them if acquire is false.
*
* Note that we don't actually try to open the relations, and hence will not
* fail if one has been dropped entirely --- we'll just transiently acquire
@@ -903,64 +1415,36 @@ plan_list_is_transient(List *stmt_list)
}
/*
- * PlanCacheComputeResultDesc: given a list of either fully-planned statements
- * or Queries, determine the result tupledesc it will produce. Returns NULL
- * if the execution will not return tuples.
+ * PlanCacheComputeResultDesc: given a list of analyzed-and-rewritten Queries,
+ * determine the result tupledesc it will produce. Returns NULL if the
+ * execution will not return tuples.
*
* Note: the result is created or copied into current memory context.
*/
-TupleDesc
+static TupleDesc
PlanCacheComputeResultDesc(List *stmt_list)
{
- Node *node;
Query *query;
- PlannedStmt *pstmt;
switch (ChoosePortalStrategy(stmt_list))
{
case PORTAL_ONE_SELECT:
case PORTAL_ONE_MOD_WITH:
- node = (Node *) linitial(stmt_list);
- if (IsA(node, Query))
- {
- query = (Query *) node;
- return ExecCleanTypeFromTL(query->targetList, false);
- }
- if (IsA(node, PlannedStmt))
- {
- pstmt = (PlannedStmt *) node;
- return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
- }
- /* other cases shouldn't happen, but return NULL */
- break;
+ query = (Query *) linitial(stmt_list);
+ Assert(IsA(query, Query));
+ return ExecCleanTypeFromTL(query->targetList, false);
case PORTAL_ONE_RETURNING:
- node = PortalListGetPrimaryStmt(stmt_list);
- if (IsA(node, Query))
- {
- query = (Query *) node;
- Assert(query->returningList);
- return ExecCleanTypeFromTL(query->returningList, false);
- }
- if (IsA(node, PlannedStmt))
- {
- pstmt = (PlannedStmt *) node;
- Assert(pstmt->hasReturning);
- return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
- }
- /* other cases shouldn't happen, but return NULL */
- break;
+ query = (Query *) PortalListGetPrimaryStmt(stmt_list);
+ Assert(IsA(query, Query));
+ Assert(query->returningList);
+ return ExecCleanTypeFromTL(query->returningList, false);
case PORTAL_UTIL_SELECT:
- node = (Node *) linitial(stmt_list);
- if (IsA(node, Query))
- {
- query = (Query *) node;
- Assert(query->utilityStmt);
- return UtilityTupleDescriptor(query->utilityStmt);
- }
- /* else it's a bare utility statement */
- return UtilityTupleDescriptor(node);
+ query = (Query *) linitial(stmt_list);
+ Assert(IsA(query, Query));
+ Assert(query->utilityStmt);
+ return UtilityTupleDescriptor(query->utilityStmt);
case PORTAL_MULTI_QUERY:
/* will not return tuples */
@@ -979,33 +1463,39 @@ PlanCacheComputeResultDesc(List *stmt_list)
static void
PlanCacheRelCallback(Datum arg, Oid relid)
{
- ListCell *lc1;
+ CachedPlanSource *plansource;
- foreach(lc1, cached_plans_list)
+ for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
{
- CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
- CachedPlan *plan = plansource->plan;
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */
- if (!plan || plan->dead)
+ if (!plansource->is_valid)
continue;
/*
- * Check the list we built ourselves; this covers unplanned cases
- * including EXPLAIN.
+ * Check the dependency list for the rewritten querytree.
*/
- if ((relid == InvalidOid) ? plan->relationOids != NIL :
- list_member_oid(plan->relationOids, relid))
- plan->dead = true;
+ if ((relid == InvalidOid) ? plansource->relationOids != NIL :
+ list_member_oid(plansource->relationOids, relid))
+ {
+ /* Invalidate the querytree and generic plan */
+ plansource->is_valid = false;
+ if (plansource->gplan)
+ plansource->gplan->is_valid = false;
+ }
- if (plan->fully_planned && !plan->dead)
+ /*
+ * The generic plan, if any, could have more dependencies than the
+ * querytree does, so we have to check it too.
+ */
+ if (plansource->gplan && plansource->gplan->is_valid)
{
- /* Have to check the per-PlannedStmt relid lists */
- ListCell *lc2;
+ ListCell *lc;
- foreach(lc2, plan->stmt_list)
+ foreach(lc, plansource->gplan->stmt_list)
{
- PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
+ PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
Assert(!IsA(plannedstmt, Query));
if (!IsA(plannedstmt, PlannedStmt))
@@ -1013,8 +1503,8 @@ PlanCacheRelCallback(Datum arg, Oid relid)
if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL :
list_member_oid(plannedstmt->relationOids, relid))
{
- /* Invalidate the plan! */
- plan->dead = true;
+ /* Invalidate the generic plan only */
+ plansource->gplan->is_valid = false;
break; /* out of stmt_list scan */
}
}
@@ -1035,43 +1525,47 @@ PlanCacheRelCallback(Datum arg, Oid relid)
static void
PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue)
{
- ListCell *lc1;
+ CachedPlanSource *plansource;
- foreach(lc1, cached_plans_list)
+ for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
{
- CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
- CachedPlan *plan = plansource->plan;
- ListCell *lc2;
+ ListCell *lc;
+
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */
- if (!plan || plan->dead)
+ if (!plansource->is_valid)
continue;
/*
- * Check the list we built ourselves; this covers unplanned cases
- * including EXPLAIN.
+ * Check the dependency list for the rewritten querytree.
*/
- foreach(lc2, plan->invalItems)
+ foreach(lc, plansource->invalItems)
{
- PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2);
+ PlanInvalItem *item = (PlanInvalItem *) lfirst(lc);
if (item->cacheId != cacheid)
continue;
if (hashvalue == 0 ||
item->hashValue == hashvalue)
{
- /* Invalidate the plan! */
- plan->dead = true;
+ /* Invalidate the querytree and generic plan */
+ plansource->is_valid = false;
+ if (plansource->gplan)
+ plansource->gplan->is_valid = false;
break;
}
}
- if (plan->fully_planned && !plan->dead)
+ /*
+ * The generic plan, if any, could have more dependencies than the
+ * querytree does, so we have to check it too.
+ */
+ if (plansource->gplan && plansource->gplan->is_valid)
{
- /* Have to check the per-PlannedStmt inval-item lists */
- foreach(lc2, plan->stmt_list)
+ foreach(lc, plansource->gplan->stmt_list)
{
- PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
+ PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
ListCell *lc3;
Assert(!IsA(plannedstmt, Query));
@@ -1086,12 +1580,12 @@ PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue)
if (hashvalue == 0 ||
item->hashValue == hashvalue)
{
- /* Invalidate the plan! */
- plan->dead = true;
+ /* Invalidate the generic plan only */
+ plansource->gplan->is_valid = false;
break; /* out of invalItems scan */
}
}
- if (plan->dead)
+ if (!plansource->gplan->is_valid)
break; /* out of stmt_list scan */
}
}
@@ -1111,65 +1605,46 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue)
}
/*
- * ResetPlanCache: drop all cached plans.
+ * ResetPlanCache: invalidate all cached plans.
*/
void
ResetPlanCache(void)
{
- ListCell *lc1;
+ CachedPlanSource *plansource;
- foreach(lc1, cached_plans_list)
+ for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
{
- CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
- CachedPlan *plan = plansource->plan;
- ListCell *lc2;
+ ListCell *lc;
+
+ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */
- if (!plan || plan->dead)
+ if (!plansource->is_valid)
continue;
/*
- * We *must not* mark transaction control statements as dead,
+ * We *must not* mark transaction control statements as invalid,
* particularly not ROLLBACK, because they may need to be executed in
* aborted transactions when we can't revalidate them (cf bug #5269).
* In general there is no point in invalidating utility statements
- * since they have no plans anyway. So mark it dead only if it
+ * since they have no plans anyway. So invalidate it only if it
* contains at least one non-utility statement. (EXPLAIN counts as a
* non-utility statement, though, since it contains an analyzed query
* that might have dependencies.)
*/
- if (plan->fully_planned)
+ foreach(lc, plansource->query_list)
{
- /* Search statement list for non-utility statements */
- foreach(lc2, plan->stmt_list)
- {
- PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
+ Query *query = (Query *) lfirst(lc);
- Assert(!IsA(plannedstmt, Query));
- if (IsA(plannedstmt, PlannedStmt) ||
- IsA(plannedstmt, ExplainStmt))
- {
- /* non-utility statement, so invalidate */
- plan->dead = true;
- break; /* out of stmt_list scan */
- }
- }
- }
- else
- {
- /* Search Query list for non-utility statements */
- foreach(lc2, plan->stmt_list)
+ Assert(IsA(query, Query));
+ if (query->commandType != CMD_UTILITY ||
+ IsA(query->utilityStmt, ExplainStmt))
{
- Query *query = (Query *) lfirst(lc2);
-
- Assert(IsA(query, Query));
- if (query->commandType != CMD_UTILITY ||
- IsA(query->utilityStmt, ExplainStmt))
- {
- /* non-utility statement, so invalidate */
- plan->dead = true;
- break; /* out of stmt_list scan */
- }
+ /* non-utility statement, so invalidate */
+ plansource->is_valid = false;
+ if (plansource->gplan)
+ plansource->gplan->is_valid = false;
+ break;
}
}
}