Logo Search packages:      
Sourcecode: postgresql-8.4 version File versions

plancache.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-2009, 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.26 2009/01/01 17:23:50 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "utils/plancache.h"
#include "access/transam.h"
#include "catalog/namespace.h"
#include "executor/executor.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/planmain.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 rowmark_member(List *rowMarks, int rt_index);
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 parameter type OIDs, or NULL if none
 * num_params: number of 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;
      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;
      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;
}

/*
 * 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 */
            plan->relationOids = plan->invalItems = NIL;
      }
      else
      {
            /* Use the planner machinery to extract dependencies */
            extract_query_dependencies(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;
            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.  Note that we do not use
             * parse_analyze_varparams(), assuming that the caller never wants the
             * parameter types to change from the original values.
             */
            slist = pg_analyze_and_rewrite(copyObject(plansource->raw_parse_tree),
                                                         plansource->query_string,
                                                         plansource->param_types,
                                                         plansource->num_params);

            if (plansource->fully_planned)
            {
                  /*
                   * Generate plans for queries.
                   */
                  slist = pg_plan_queries(slist, plansource->cursor_options, NULL);
            }

            /*
             * 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))
                  continue;               /* Ignore utility statements */

            rt_index = 0;
            foreach(lc2, plannedstmt->rtable)
            {
                  RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
                  LOCKMODE    lockmode;

                  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 (rowmark_member(plannedstmt->rowMarks, rt_index))
                        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));
            ScanQueryForLocks(query, acquire);
      }
}

/*
 * ScanQueryForLocks: recursively scan one Query for AcquirePlannerLocks.
 */
static void
ScanQueryForLocks(Query *parsetree, bool acquire)
{
      ListCell   *lc;
      int               rt_index;

      /*
       * 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 (rowmark_member(parsetree->rowMarks, rt_index))
                              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);
}

/*
 * rowmark_member: check whether an RT index appears in a RowMarkClause list.
 */
static bool
rowmark_member(List *rowMarks, int rt_index)
{
      ListCell   *l;

      foreach(l, rowMarks)
      {
            RowMarkClause *rc = (RowMarkClause *) lfirst(l);

            if (rc->rti == rt_index)
                  return true;
      }
      return false;
}

/*
 * 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->returningLists);
                        return ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists), 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;
            if (plan->fully_planned)
            {
                  /* 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 */
                        }
                  }
            }
            else
            {
                  /* Otherwise check the single list we built ourselves */
                  if ((relid == InvalidOid) ? plan->relationOids != NIL :
                        list_member_oid(plan->relationOids, relid))
                        plan->dead = true;
            }
      }
}

/*
 * 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;

            /* No work if it's already invalidated */
            if (!plan || plan->dead)
                  continue;
            if (plan->fully_planned)
            {
                  /* Have to check the per-PlannedStmt inval-item lists */
                  ListCell   *lc2;

                  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 */
                  }
            }
            else
            {
                  /* Otherwise check the single list we built ourselves */
                  ListCell   *lc2;

                  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;
                        }
                  }
            }
      }
}

/*
 * 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   *lc;

      foreach(lc, cached_plans_list)
      {
            CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
            CachedPlan *plan = plansource->plan;

            if (plan)
                  plan->dead = true;
      }
}

Generated by  Doxygen 1.6.0   Back to index