/*------------------------------------------------------------------------- * * 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.33 2010/01/13 16:56:56 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 */ 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; 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)) continue; /* Ignore utility statements */ 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)); 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 (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; 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 *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. */ 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)) { /* 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) { /* non-utility statement, so invalidate */ plan->dead = true; break; /* out of stmt_list scan */ } } } } }