mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-08 08:09:20 +02:00
parse analysis phase, rather than at execution time. This makes parameter handling work the same as it does in ordinary plannable queries, and in particular fixes the incompatibility that Pavel pointed out with plpgsql's new handling of variable references. plancache.c gets a little bit grottier, but the alternatives seem worse.
1177 lines
33 KiB
C
1177 lines
33 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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 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
|
|
* 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.
|
|
*
|
|
* Currently, we track exactly the dependencies of plans on relations and
|
|
* user-defined functions. On relcache invalidation events or pg_proc
|
|
* syscache invalidation events, we invalidate just those plans that depend
|
|
* on the particular object being modified. (Note: this scheme assumes
|
|
* that any table modification that requires replanning will generate a
|
|
* relcache inval event.) We also watch for inval events on certain other
|
|
* system catalogs, such as pg_namespace; but for them, our response is
|
|
* just to invalidate all plans. We expect updates on those catalogs to
|
|
* be infrequent enough that more-detailed tracking is not worth the effort.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.34 2010/01/15 22:36:34 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "utils/plancache.h"
|
|
#include "access/transam.h"
|
|
#include "catalog/namespace.h"
|
|
#include "executor/executor.h"
|
|
#include "executor/spi.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "optimizer/planmain.h"
|
|
#include "optimizer/prep.h"
|
|
#include "parser/parsetree.h"
|
|
#include "storage/lmgr.h"
|
|
#include "tcop/pquery.h"
|
|
#include "tcop/tcopprot.h"
|
|
#include "tcop/utility.h"
|
|
#include "utils/inval.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/resowner.h"
|
|
#include "utils/snapmgr.h"
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
static List *cached_plans_list = NIL;
|
|
|
|
static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list,
|
|
MemoryContext plan_context);
|
|
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 void PlanCacheRelCallback(Datum arg, Oid relid);
|
|
static void PlanCacheFuncCallback(Datum arg, int cacheid, ItemPointer tuplePtr);
|
|
static void PlanCacheSysCallback(Datum arg, int cacheid, ItemPointer tuplePtr);
|
|
|
|
|
|
/*
|
|
* InitPlanCache: initialize module during InitPostgres.
|
|
*
|
|
* All we need to do is hook into inval.c's callback lists.
|
|
*/
|
|
void
|
|
InitPlanCache(void)
|
|
{
|
|
CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0);
|
|
CacheRegisterSyscacheCallback(PROCOID, PlanCacheFuncCallback, (Datum) 0);
|
|
CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0);
|
|
CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0);
|
|
CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* raw_parse_tree: output of raw_parser()
|
|
* query_string: original query text (as of PG 8.4, must not be NULL)
|
|
* 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)
|
|
{
|
|
CachedPlanSource *plansource;
|
|
OverrideSearchPath *search_path;
|
|
MemoryContext source_context;
|
|
MemoryContext oldcxt;
|
|
|
|
Assert(query_string != NULL); /* required as of 8.4 */
|
|
|
|
/*
|
|
* Make a dedicated memory context for the CachedPlanSource and its
|
|
* subsidiary data. We expect it can be pretty small.
|
|
*/
|
|
source_context = AllocSetContextCreate(CacheMemoryContext,
|
|
"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);
|
|
|
|
/*
|
|
* Create and fill the CachedPlanSource struct within the new context.
|
|
*/
|
|
oldcxt = MemoryContextSwitchTo(source_context);
|
|
plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
|
|
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->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->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);
|
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
|
|
return plansource;
|
|
}
|
|
|
|
/*
|
|
* FastCreateCachedPlan: create a plan cache entry with minimal data copying.
|
|
*
|
|
* 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.)
|
|
*
|
|
* 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.
|
|
*/
|
|
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)
|
|
{
|
|
CachedPlanSource *plansource;
|
|
OverrideSearchPath *search_path;
|
|
MemoryContext oldcxt;
|
|
|
|
Assert(query_string != NULL); /* required as of 8.4 */
|
|
|
|
/*
|
|
* Fetch current search_path into given context, but do any recalculation
|
|
* work required in caller's context.
|
|
*/
|
|
search_path = GetOverrideSearchPath(context);
|
|
|
|
/*
|
|
* Create and fill the CachedPlanSource struct within the given context.
|
|
*/
|
|
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;
|
|
plansource->num_params = num_params;
|
|
/* these can be set later with CachedPlanSetParserHook: */
|
|
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->context = context;
|
|
plansource->orig_plan = NULL;
|
|
|
|
/*
|
|
* Store the current output plans into the plancache entry.
|
|
*/
|
|
StoreCachedPlan(plansource, stmt_list, context);
|
|
|
|
/*
|
|
* Since the context is owned by the CachedPlan, advance its refcount.
|
|
*/
|
|
plansource->orig_plan = plansource->plan;
|
|
plansource->orig_plan->refcount++;
|
|
|
|
/*
|
|
* 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);
|
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
|
|
return plansource;
|
|
}
|
|
|
|
/*
|
|
* CachedPlanSetParserHook: set up to use parser callback hooks
|
|
*
|
|
* 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!
|
|
*/
|
|
void
|
|
CachedPlanSetParserHook(CachedPlanSource *plansource,
|
|
ParserSetupHook parserSetup,
|
|
void *parserSetupArg)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
/*
|
|
* StoreCachedPlan: store a built or rebuilt plan into a plancache entry.
|
|
*
|
|
* Common subroutine for CreateCachedPlan and RevalidateCachedPlan.
|
|
*/
|
|
static void
|
|
StoreCachedPlan(CachedPlanSource *plansource,
|
|
List *stmt_list,
|
|
MemoryContext plan_context)
|
|
{
|
|
CachedPlan *plan;
|
|
MemoryContext oldcxt;
|
|
|
|
if (plan_context == NULL)
|
|
{
|
|
/*
|
|
* 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);
|
|
|
|
/*
|
|
* Copy supplied data into the new context.
|
|
*/
|
|
oldcxt = MemoryContextSwitchTo(plan_context);
|
|
|
|
stmt_list = (List *) copyObject(stmt_list);
|
|
}
|
|
else
|
|
{
|
|
/* Assume subsidiary data is in the given context */
|
|
oldcxt = MemoryContextSwitchTo(plan_context);
|
|
}
|
|
|
|
/*
|
|
* Create and fill the CachedPlan struct within the new context.
|
|
*/
|
|
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(TransactionIdIsNormal(TransactionXmin));
|
|
plan->saved_xmin = TransactionXmin;
|
|
}
|
|
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;
|
|
|
|
if (list_length(stmt_list) == 1 &&
|
|
IsA(linitial(stmt_list), ExplainStmt))
|
|
{
|
|
ExplainStmt *estmt = (ExplainStmt *) linitial(stmt_list);
|
|
|
|
extract_query_dependencies(estmt->query,
|
|
&plan->relationOids,
|
|
&plan->invalItems);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Use the planner machinery to extract dependencies */
|
|
extract_query_dependencies((Node *) stmt_list,
|
|
&plan->relationOids,
|
|
&plan->invalItems);
|
|
}
|
|
|
|
Assert(plansource->plan == NULL);
|
|
plansource->plan = plan;
|
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
}
|
|
|
|
/*
|
|
* 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));
|
|
|
|
/* Remove it from the list */
|
|
cached_plans_list = list_delete_ptr(cached_plans_list, plansource);
|
|
|
|
/* Decrement child CachePlan's refcount and drop if no longer needed */
|
|
if (plansource->plan)
|
|
ReleaseCachedPlan(plansource->plan, false);
|
|
|
|
/*
|
|
* If CachedPlanSource has independent storage, just drop it. Otherwise
|
|
* decrement the refcount on the CachePlan that owns the storage.
|
|
*/
|
|
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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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).
|
|
*
|
|
* 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: if any replanning activity is required, the caller's memory context
|
|
* is used for that work.
|
|
*/
|
|
CachedPlan *
|
|
RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
|
|
{
|
|
CachedPlan *plan;
|
|
|
|
/* Validity check that we were given a CachedPlanSource */
|
|
Assert(list_member_ptr(cached_plans_list, plansource));
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
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);
|
|
|
|
if (plan->fully_planned)
|
|
AcquireExecutorLocks(plan->stmt_list, true);
|
|
else
|
|
AcquirePlannerLocks(plan->stmt_list, true);
|
|
|
|
/*
|
|
* If plan was transient, check to see if TransactionXmin has
|
|
* advanced, and if so invalidate it.
|
|
*/
|
|
if (!plan->dead &&
|
|
TransactionIdIsValid(plan->saved_xmin) &&
|
|
!TransactionIdEquals(plan->saved_xmin, TransactionXmin))
|
|
plan->dead = true;
|
|
|
|
/*
|
|
* By now, if any invalidation has happened, the inval callback
|
|
* functions will have marked the plan dead.
|
|
*/
|
|
if (plan->dead)
|
|
{
|
|
/* Ooops, the race case happened. Release useless locks. */
|
|
if (plan->fully_planned)
|
|
AcquireExecutorLocks(plan->stmt_list, false);
|
|
else
|
|
AcquirePlannerLocks(plan->stmt_list, false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If plan has been invalidated, unlink it from the parent and release it.
|
|
*/
|
|
if (plan && plan->dead)
|
|
{
|
|
plansource->plan = NULL;
|
|
ReleaseCachedPlan(plan, false);
|
|
plan = NULL;
|
|
}
|
|
|
|
/*
|
|
* Build a new plan if needed.
|
|
*/
|
|
if (!plan)
|
|
{
|
|
bool snapshot_set = false;
|
|
Node *rawtree;
|
|
List *slist;
|
|
TupleDesc resultDesc;
|
|
|
|
/*
|
|
* Restore the search_path that was in use when the plan was made.
|
|
* (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.
|
|
*/
|
|
if (!ActiveSnapshotSet())
|
|
{
|
|
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)
|
|
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);
|
|
|
|
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;
|
|
|
|
pushed = SPI_push_conditional();
|
|
|
|
slist = pg_plan_queries(slist, plansource->cursor_options, NULL);
|
|
|
|
SPI_pop_conditional(pushed);
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
/* OK, doesn't return tuples */
|
|
}
|
|
else if (resultDesc == NULL || plansource->resultDesc == NULL ||
|
|
!equalTupleDescs(resultDesc, plansource->resultDesc))
|
|
{
|
|
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();
|
|
|
|
/*
|
|
* Store the plans into the plancache entry, advancing the generation
|
|
* count.
|
|
*/
|
|
StoreCachedPlan(plansource, slist, NULL);
|
|
|
|
plan = plansource->plan;
|
|
}
|
|
|
|
/*
|
|
* Last step: flag the plan as in use by caller.
|
|
*/
|
|
if (useResOwner)
|
|
ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
|
|
plan->refcount++;
|
|
if (useResOwner)
|
|
ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
|
|
|
|
return plan;
|
|
}
|
|
|
|
/*
|
|
* ReleaseCachedPlan: release active use of a cached plan.
|
|
*
|
|
* This decrements the reference count, and frees the plan if the count
|
|
* has thereby gone to zero. If useResOwner is true, it is assumed that
|
|
* the reference count is managed by the CurrentResourceOwner.
|
|
*
|
|
* Note: useResOwner = false is used for releasing references that are in
|
|
* persistent data structures, such as the parent CachedPlanSource or a
|
|
* Portal. Transient references should be protected by a resource owner.
|
|
*/
|
|
void
|
|
ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
|
|
{
|
|
if (useResOwner)
|
|
ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan);
|
|
Assert(plan->refcount > 0);
|
|
plan->refcount--;
|
|
if (plan->refcount == 0)
|
|
MemoryContextDelete(plan->context);
|
|
}
|
|
|
|
/*
|
|
* CachedPlanIsValid: test whether the plan 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.
|
|
*/
|
|
bool
|
|
CachedPlanIsValid(CachedPlanSource *plansource)
|
|
{
|
|
CachedPlan *plan;
|
|
|
|
/* Validity check that we were given a CachedPlanSource */
|
|
Assert(list_member_ptr(cached_plans_list, plansource));
|
|
|
|
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);
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* AcquireExecutorLocks: acquire locks needed for execution of a fully-planned
|
|
* cached plan; or release them if acquire is false.
|
|
*/
|
|
static void
|
|
AcquireExecutorLocks(List *stmt_list, bool acquire)
|
|
{
|
|
ListCell *lc1;
|
|
|
|
foreach(lc1, stmt_list)
|
|
{
|
|
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc1);
|
|
int rt_index;
|
|
ListCell *lc2;
|
|
|
|
Assert(!IsA(plannedstmt, Query));
|
|
if (!IsA(plannedstmt, PlannedStmt))
|
|
{
|
|
/*
|
|
* Ignore utility statements, except EXPLAIN which contains a
|
|
* parsed-but-not-planned query. Note: it's okay to use
|
|
* ScanQueryForLocks, even though the query hasn't been through
|
|
* rule rewriting, because rewriting doesn't change the query
|
|
* representation.
|
|
*/
|
|
if (IsA(plannedstmt, ExplainStmt))
|
|
{
|
|
Query *query;
|
|
|
|
query = (Query *) ((ExplainStmt *) plannedstmt)->query;
|
|
Assert(IsA(query, Query));
|
|
ScanQueryForLocks(query, acquire);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
rt_index = 0;
|
|
foreach(lc2, plannedstmt->rtable)
|
|
{
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
|
|
LOCKMODE lockmode;
|
|
PlanRowMark *rc;
|
|
|
|
rt_index++;
|
|
|
|
if (rte->rtekind != RTE_RELATION)
|
|
continue;
|
|
|
|
/*
|
|
* Acquire the appropriate type of lock on each relation OID. Note
|
|
* that we don't actually try to open the rel, and hence will not
|
|
* fail if it's been dropped entirely --- we'll just transiently
|
|
* acquire a non-conflicting lock.
|
|
*/
|
|
if (list_member_int(plannedstmt->resultRelations, rt_index))
|
|
lockmode = RowExclusiveLock;
|
|
else if ((rc = get_plan_rowmark(plannedstmt->rowMarks, rt_index)) != NULL &&
|
|
RowMarkRequiresRowShareLock(rc->markType))
|
|
lockmode = RowShareLock;
|
|
else
|
|
lockmode = AccessShareLock;
|
|
|
|
if (acquire)
|
|
LockRelationOid(rte->relid, lockmode);
|
|
else
|
|
UnlockRelationOid(rte->relid, lockmode);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* AcquirePlannerLocks: acquire locks needed for planning and execution of a
|
|
* not-fully-planned cached plan; 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
|
|
* a non-conflicting lock.
|
|
*/
|
|
static void
|
|
AcquirePlannerLocks(List *stmt_list, bool acquire)
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, stmt_list)
|
|
{
|
|
Query *query = (Query *) lfirst(lc);
|
|
|
|
Assert(IsA(query, Query));
|
|
|
|
if (query->commandType == CMD_UTILITY)
|
|
{
|
|
/* Ignore utility statements, except EXPLAIN */
|
|
if (IsA(query->utilityStmt, ExplainStmt))
|
|
{
|
|
query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
|
|
Assert(IsA(query, Query));
|
|
ScanQueryForLocks(query, acquire);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
ScanQueryForLocks(query, acquire);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ScanQueryForLocks: recursively scan one Query for AcquirePlannerLocks.
|
|
*/
|
|
static void
|
|
ScanQueryForLocks(Query *parsetree, bool acquire)
|
|
{
|
|
ListCell *lc;
|
|
int rt_index;
|
|
|
|
/* Shouldn't get called on utility commands */
|
|
Assert(parsetree->commandType != CMD_UTILITY);
|
|
|
|
/*
|
|
* First, process RTEs of the current query level.
|
|
*/
|
|
rt_index = 0;
|
|
foreach(lc, parsetree->rtable)
|
|
{
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
|
|
LOCKMODE lockmode;
|
|
|
|
rt_index++;
|
|
switch (rte->rtekind)
|
|
{
|
|
case RTE_RELATION:
|
|
/* Acquire or release the appropriate type of lock */
|
|
if (rt_index == parsetree->resultRelation)
|
|
lockmode = RowExclusiveLock;
|
|
else if (get_parse_rowmark(parsetree, rt_index) != NULL)
|
|
lockmode = RowShareLock;
|
|
else
|
|
lockmode = AccessShareLock;
|
|
if (acquire)
|
|
LockRelationOid(rte->relid, lockmode);
|
|
else
|
|
UnlockRelationOid(rte->relid, lockmode);
|
|
break;
|
|
|
|
case RTE_SUBQUERY:
|
|
/* Recurse into subquery-in-FROM */
|
|
ScanQueryForLocks(rte->subquery, acquire);
|
|
break;
|
|
|
|
default:
|
|
/* ignore other types of RTEs */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Recurse into subquery-in-WITH */
|
|
foreach(lc, parsetree->cteList)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
|
|
|
|
ScanQueryForLocks((Query *) cte->ctequery, acquire);
|
|
}
|
|
|
|
/*
|
|
* Recurse into sublink subqueries, too. But we already did the ones in
|
|
* the rtable and cteList.
|
|
*/
|
|
if (parsetree->hasSubLinks)
|
|
{
|
|
query_tree_walker(parsetree, ScanQueryWalker,
|
|
(void *) &acquire,
|
|
QTW_IGNORE_RC_SUBQUERIES);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Walker to find sublink subqueries for ScanQueryForLocks
|
|
*/
|
|
static bool
|
|
ScanQueryWalker(Node *node, bool *acquire)
|
|
{
|
|
if (node == NULL)
|
|
return false;
|
|
if (IsA(node, SubLink))
|
|
{
|
|
SubLink *sub = (SubLink *) node;
|
|
|
|
/* Do what we came for */
|
|
ScanQueryForLocks((Query *) sub->subselect, *acquire);
|
|
/* Fall through to process lefthand args of SubLink */
|
|
}
|
|
|
|
/*
|
|
* Do NOT recurse into Query nodes, because ScanQueryForLocks already
|
|
* processed subselects of subselects for us.
|
|
*/
|
|
return expression_tree_walker(node, ScanQueryWalker,
|
|
(void *) acquire);
|
|
}
|
|
|
|
/*
|
|
* plan_list_is_transient: check if any of the plans in the list are transient.
|
|
*/
|
|
static bool
|
|
plan_list_is_transient(List *stmt_list)
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, stmt_list)
|
|
{
|
|
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
|
|
|
|
if (!IsA(plannedstmt, PlannedStmt))
|
|
continue; /* Ignore utility statements */
|
|
|
|
if (plannedstmt->transientPlan)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* Note: the result is created or copied into current memory context.
|
|
*/
|
|
TupleDesc
|
|
PlanCacheComputeResultDesc(List *stmt_list)
|
|
{
|
|
Node *node;
|
|
Query *query;
|
|
PlannedStmt *pstmt;
|
|
|
|
switch (ChoosePortalStrategy(stmt_list))
|
|
{
|
|
case PORTAL_ONE_SELECT:
|
|
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;
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
case PORTAL_MULTI_QUERY:
|
|
/* will not return tuples */
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* PlanCacheRelCallback
|
|
* Relcache inval callback function
|
|
*
|
|
* Invalidate all plans mentioning the given rel, or all plans mentioning
|
|
* any rel at all if relid == InvalidOid.
|
|
*/
|
|
static void
|
|
PlanCacheRelCallback(Datum arg, Oid relid)
|
|
{
|
|
ListCell *lc1;
|
|
|
|
foreach(lc1, cached_plans_list)
|
|
{
|
|
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
|
|
CachedPlan *plan = plansource->plan;
|
|
|
|
/* No work if it's already invalidated */
|
|
if (!plan || plan->dead)
|
|
continue;
|
|
|
|
/*
|
|
* Check the list we built ourselves; this covers unplanned cases
|
|
* including EXPLAIN.
|
|
*/
|
|
if ((relid == InvalidOid) ? plan->relationOids != NIL :
|
|
list_member_oid(plan->relationOids, relid))
|
|
plan->dead = true;
|
|
|
|
if (plan->fully_planned && !plan->dead)
|
|
{
|
|
/* Have to check the per-PlannedStmt relid lists */
|
|
ListCell *lc2;
|
|
|
|
foreach(lc2, plan->stmt_list)
|
|
{
|
|
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
|
|
|
|
Assert(!IsA(plannedstmt, Query));
|
|
if (!IsA(plannedstmt, PlannedStmt))
|
|
continue; /* Ignore utility statements */
|
|
if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL :
|
|
list_member_oid(plannedstmt->relationOids, relid))
|
|
{
|
|
/* Invalidate the plan! */
|
|
plan->dead = true;
|
|
break; /* out of stmt_list scan */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* PlanCacheFuncCallback
|
|
* Syscache inval callback function for PROCOID cache
|
|
*
|
|
* Invalidate all plans mentioning the given catalog entry, or all plans
|
|
* mentioning any member of this cache if tuplePtr == NULL.
|
|
*
|
|
* Note that the coding would support use for multiple caches, but right
|
|
* now only user-defined functions are tracked this way.
|
|
*/
|
|
static void
|
|
PlanCacheFuncCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
|
|
{
|
|
ListCell *lc1;
|
|
|
|
foreach(lc1, cached_plans_list)
|
|
{
|
|
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
|
|
CachedPlan *plan = plansource->plan;
|
|
ListCell *lc2;
|
|
|
|
/* No work if it's already invalidated */
|
|
if (!plan || plan->dead)
|
|
continue;
|
|
|
|
/*
|
|
* Check the list we built ourselves; this covers unplanned cases
|
|
* including EXPLAIN.
|
|
*/
|
|
foreach(lc2, plan->invalItems)
|
|
{
|
|
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2);
|
|
|
|
if (item->cacheId != cacheid)
|
|
continue;
|
|
if (tuplePtr == NULL ||
|
|
ItemPointerEquals(tuplePtr, &item->tupleId))
|
|
{
|
|
/* Invalidate the plan! */
|
|
plan->dead = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (plan->fully_planned && !plan->dead)
|
|
{
|
|
/* Have to check the per-PlannedStmt inval-item lists */
|
|
foreach(lc2, plan->stmt_list)
|
|
{
|
|
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
|
|
ListCell *lc3;
|
|
|
|
Assert(!IsA(plannedstmt, Query));
|
|
if (!IsA(plannedstmt, PlannedStmt))
|
|
continue; /* Ignore utility statements */
|
|
foreach(lc3, plannedstmt->invalItems)
|
|
{
|
|
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc3);
|
|
|
|
if (item->cacheId != cacheid)
|
|
continue;
|
|
if (tuplePtr == NULL ||
|
|
ItemPointerEquals(tuplePtr, &item->tupleId))
|
|
{
|
|
/* Invalidate the plan! */
|
|
plan->dead = true;
|
|
break; /* out of invalItems scan */
|
|
}
|
|
}
|
|
if (plan->dead)
|
|
break; /* out of stmt_list scan */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* PlanCacheSysCallback
|
|
* Syscache inval callback function for other caches
|
|
*
|
|
* Just invalidate everything...
|
|
*/
|
|
static void
|
|
PlanCacheSysCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
|
|
{
|
|
ResetPlanCache();
|
|
}
|
|
|
|
/*
|
|
* ResetPlanCache: drop all cached plans.
|
|
*/
|
|
void
|
|
ResetPlanCache(void)
|
|
{
|
|
ListCell *lc1;
|
|
|
|
foreach(lc1, cached_plans_list)
|
|
{
|
|
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
|
|
CachedPlan *plan = plansource->plan;
|
|
ListCell *lc2;
|
|
|
|
/* No work if it's already invalidated */
|
|
if (!plan || plan->dead)
|
|
continue;
|
|
|
|
/*
|
|
* We *must not* mark transaction control statements as dead,
|
|
* 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
|
|
* 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)
|
|
{
|
|
/* Search statement list for non-utility statements */
|
|
foreach(lc2, plan->stmt_list)
|
|
{
|
|
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
|
|
|
|
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)
|
|
{
|
|
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 */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|