/*------------------------------------------------------------------------- * * plancache.c * Plan cache management. * * The plan cache manager has two principal responsibilities: deciding when * to use a generic plan versus a custom (parameter-value-specific) plan, * and tracking whether cached plans need to be invalidated because of schema * changes in the objects they depend on. * * The logic for choosing generic or custom plans is in choose_custom_plan, * which see for comments. * * Cache invalidation is driven off sinval events. Any CachedPlanSource * that matches the event is marked invalid, as is its generic CachedPlan * if it has one. When (and if) the next demand for a cached plan occurs, * parse analysis and/or rewrite is repeated to build a new valid query tree, * and then planning is performed as normal. We also force re-analysis and * re-planning if the active search_path is different from the previous time * or, if RLS is involved, if the user changes or the RLS environment changes. * * Note that if the sinval was a result of user DDL actions, parse analysis * could throw an error, for example if a column referenced by the query is * no longer present. Another possibility is for the query's output tupdesc * to change (for instance "SELECT *" might expand differently than before). * The creator of a cached plan can specify whether it is allowable for the * query to change output tupdesc on replan --- 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, * user-defined functions, and domains. On relcache invalidation events or * pg_proc or pg_type 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. * * In addition to full-fledged query plans, we provide a facility for * detecting invalidations of simple scalar expressions. This is fairly * bare-bones; it's the caller's responsibility to build a new expression * if the old one gets invalidated. * * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/backend/utils/cache/plancache.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include "access/transam.h" #include "catalog/namespace.h" #include "executor/executor.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "parser/analyze.h" #include "rewrite/rewriteHandler.h" #include "storage/lmgr.h" #include "tcop/pquery.h" #include "tcop/utility.h" #include "utils/inval.h" #include "utils/memutils.h" #include "utils/resowner.h" #include "utils/rls.h" #include "utils/snapmgr.h" #include "utils/syscache.h" /* * This is the head of the backend's list of "saved" CachedPlanSources (i.e., * those that are in long-lived storage and are examined for sinval events). * We use a dlist instead of separate List cells so that we can guarantee * to save a CachedPlanSource without error. */ static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list); /* * This is the head of the backend's list of CachedExpressions. */ static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list); static void ReleaseGenericPlan(CachedPlanSource *plansource); static bool StmtPlanRequiresRevalidation(CachedPlanSource *plansource); static bool BuildingPlanRequiresSnapshot(CachedPlanSource *plansource); static List *RevalidateCachedQuery(CachedPlanSource *plansource, QueryEnvironment *queryEnv, bool release_generic); static bool CheckCachedPlan(CachedPlanSource *plansource); static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist, ParamListInfo boundParams, QueryEnvironment *queryEnv); static bool choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams); static double cached_plan_cost(CachedPlan *plan, bool include_planner); static Query *QueryListGetPrimaryStmt(List *stmts); 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 TupleDesc PlanCacheComputeResultDesc(List *stmt_list); static void PlanCacheRelCallback(Datum arg, Oid relid); static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); /* ResourceOwner callbacks to track plancache references */ static void ResOwnerReleaseCachedPlan(Datum res); static const ResourceOwnerDesc planref_resowner_desc = { .name = "plancache reference", .release_phase = RESOURCE_RELEASE_AFTER_LOCKS, .release_priority = RELEASE_PRIO_PLANCACHE_REFS, .ReleaseResource = ResOwnerReleaseCachedPlan, .DebugPrint = NULL /* the default message is fine */ }; /* Convenience wrappers over ResourceOwnerRemember/Forget */ static inline void ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan) { ResourceOwnerRemember(owner, PointerGetDatum(plan), &planref_resowner_desc); } static inline void ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan) { ResourceOwnerForget(owner, PointerGetDatum(plan), &planref_resowner_desc); } /* GUC parameter */ int plan_cache_mode = PLAN_CACHE_MODE_AUTO; /* * 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, PlanCacheObjectCallback, (Datum) 0); CacheRegisterSyscacheCallback(TYPEOID, PlanCacheObjectCallback, (Datum) 0); CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0); } /* * CreateCachedPlan: initially create a plan cache entry for a raw parse tree. * * Creation of a cached plan is divided into two steps, CreateCachedPlan and * CompleteCachedPlan. CreateCachedPlan should be called after running the * query through raw_parser, but before doing parse analysis and rewrite; * CompleteCachedPlan is called after that. The reason for this arrangement * is that it can save one round of copying of the raw parse tree, since * the parser will normally scribble on the raw parse tree. Callers would * otherwise need to make an extra copy of the parse tree to ensure they * still had a clean copy to present at plan cache creation time. * * All arguments presented to CreateCachedPlan are copied into a memory * context created as a child of the call-time CurrentMemoryContext, which * should be a reasonably short-lived working context that will go away in * event of an error. This ensures that the cached plan data structure will * likewise disappear if an error occurs before we have fully constructed it. * Once constructed, the cached plan can be made longer-lived, if needed, * by calling SaveCachedPlan. * * raw_parse_tree: output of raw_parser(), or NULL if empty query * query_string: original query text * commandTag: command tag for query, or UNKNOWN if empty query */ CachedPlanSource * CreateCachedPlan(RawStmt *raw_parse_tree, const char *query_string, CommandTag commandTag) { CachedPlanSource *plansource; MemoryContext source_context; MemoryContext oldcxt; Assert(query_string != NULL); /* required as of 8.4 */ /* * Make a dedicated memory context for the CachedPlanSource and its * permanent subsidiary data. It's probably not going to be large, but * just in case, allow it to grow large. Initially it's a child of the * caller's context (which we assume to be transient), so that it will be * cleaned up on error. */ source_context = AllocSetContextCreate(CurrentMemoryContext, "CachedPlanSource", ALLOCSET_START_SMALL_SIZES); /* * Create and fill the CachedPlanSource struct within the new context. * Most fields are just left empty for the moment. */ oldcxt = MemoryContextSwitchTo(source_context); plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); plansource->magic = CACHEDPLANSOURCE_MAGIC; plansource->raw_parse_tree = copyObject(raw_parse_tree); plansource->analyzed_parse_tree = NULL; plansource->query_string = pstrdup(query_string); MemoryContextSetIdentifier(source_context, plansource->query_string); plansource->commandTag = commandTag; plansource->param_types = NULL; plansource->num_params = 0; plansource->parserSetup = NULL; plansource->parserSetupArg = NULL; plansource->postRewrite = NULL; plansource->postRewriteArg = NULL; plansource->cursor_options = 0; plansource->fixed_result = false; plansource->resultDesc = NULL; plansource->context = source_context; plansource->query_list = NIL; plansource->relationOids = NIL; plansource->invalItems = NIL; plansource->search_path = NULL; plansource->query_context = NULL; plansource->rewriteRoleId = InvalidOid; plansource->rewriteRowSecurity = false; plansource->dependsOnRLS = false; plansource->gplan = NULL; plansource->is_oneshot = false; plansource->is_complete = false; plansource->is_saved = false; plansource->is_valid = false; plansource->generation = 0; plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_generic_plans = 0; plansource->num_custom_plans = 0; MemoryContextSwitchTo(oldcxt); return plansource; } /* * CreateCachedPlanForQuery: initially create a plan cache entry for a Query. * * This is used in the same way as CreateCachedPlan, except that the source * query has already been through parse analysis, and the plancache will never * try to re-do that step. * * Currently this is used only for new-style SQL functions, where we have a * Query from the function's prosqlbody, but no source text. The query_string * is typically empty, but is required anyway. */ CachedPlanSource * CreateCachedPlanForQuery(Query *analyzed_parse_tree, const char *query_string, CommandTag commandTag) { CachedPlanSource *plansource; MemoryContext oldcxt; /* Rather than duplicating CreateCachedPlan, just do this: */ plansource = CreateCachedPlan(NULL, query_string, commandTag); oldcxt = MemoryContextSwitchTo(plansource->context); plansource->analyzed_parse_tree = copyObject(analyzed_parse_tree); MemoryContextSwitchTo(oldcxt); return plansource; } /* * CreateOneShotCachedPlan: initially create a one-shot plan cache entry. * * This variant of CreateCachedPlan creates a plan cache entry that is meant * to be used only once. No data copying occurs: all data structures remain * in the caller's memory context (which typically should get cleared after * completing execution). The CachedPlanSource struct itself is also created * in that context. * * A one-shot plan cannot be saved or copied, since we make no effort to * preserve the raw parse tree unmodified. There is also no support for * invalidation, so plan use must be completed in the current transaction, * and DDL that might invalidate the querytree_list must be avoided as well. * * raw_parse_tree: output of raw_parser(), or NULL if empty query * query_string: original query text * commandTag: command tag for query, or NULL if empty query */ CachedPlanSource * CreateOneShotCachedPlan(RawStmt *raw_parse_tree, const char *query_string, CommandTag commandTag) { CachedPlanSource *plansource; Assert(query_string != NULL); /* required as of 8.4 */ /* * Create and fill the CachedPlanSource struct within the caller's memory * context. Most fields are just left empty for the moment. */ plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); plansource->magic = CACHEDPLANSOURCE_MAGIC; plansource->raw_parse_tree = raw_parse_tree; plansource->analyzed_parse_tree = NULL; plansource->query_string = query_string; plansource->commandTag = commandTag; plansource->param_types = NULL; plansource->num_params = 0; plansource->parserSetup = NULL; plansource->parserSetupArg = NULL; plansource->postRewrite = NULL; plansource->postRewriteArg = NULL; plansource->cursor_options = 0; plansource->fixed_result = false; plansource->resultDesc = NULL; plansource->context = CurrentMemoryContext; plansource->query_list = NIL; plansource->relationOids = NIL; plansource->invalItems = NIL; plansource->search_path = NULL; plansource->query_context = NULL; plansource->rewriteRoleId = InvalidOid; plansource->rewriteRowSecurity = false; plansource->dependsOnRLS = false; plansource->gplan = NULL; plansource->is_oneshot = true; plansource->is_complete = false; plansource->is_saved = false; plansource->is_valid = false; plansource->generation = 0; plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_generic_plans = 0; plansource->num_custom_plans = 0; return plansource; } /* * CompleteCachedPlan: second step of creating a plan cache entry. * * Pass in the analyzed-and-rewritten form of the query, as well as the * required subsidiary data about parameters and such. All passed values will * be copied into the CachedPlanSource's memory, except as specified below. * After this is called, GetCachedPlan can be called to obtain a plan, and * optionally the CachedPlanSource can be saved using SaveCachedPlan. * * If querytree_context is not NULL, the querytree_list must be stored in that * context (but the other parameters need not be). The querytree_list is not * copied, rather the given context is kept as the initial query_context of * the CachedPlanSource. (It should have been created as a child of the * caller's working memory context, but it will now be reparented to belong * to the CachedPlanSource.) The querytree_context is normally the context in * which the caller did raw parsing and parse analysis. This approach saves * one tree copying step compared to passing NULL, but leaves lots of extra * cruft in the query_context, namely whatever extraneous stuff parse analysis * created, as well as whatever went unused from the raw parse tree. Using * this option is a space-for-time tradeoff that is appropriate if the * CachedPlanSource is not expected to survive long. * * plancache.c cannot know how to copy the data referenced by parserSetupArg, * and it would often be inappropriate to do so anyway. When using that * option, it is caller's responsibility that the referenced data remains * valid for as long as the CachedPlanSource exists. * * If the CachedPlanSource is a "oneshot" plan, then no querytree copying * occurs at all, and querytree_context is ignored; it is caller's * responsibility that the passed querytree_list is sufficiently long-lived. * * plansource: structure returned by CreateCachedPlan * querytree_list: analyzed-and-rewritten form of query (list of Query nodes) * querytree_context: memory context containing querytree_list, * or NULL to copy querytree_list into a fresh context * param_types: array of fixed parameter type OIDs, or NULL if none * num_params: number of fixed parameters * parserSetup: alternate method for handling query parameters * parserSetupArg: data to pass to parserSetup * cursor_options: options bitmask to pass to planner * fixed_result: true to disallow future changes in query's result tupdesc */ void CompleteCachedPlan(CachedPlanSource *plansource, List *querytree_list, MemoryContext querytree_context, Oid *param_types, int num_params, ParserSetupHook parserSetup, void *parserSetupArg, int cursor_options, bool fixed_result) { MemoryContext source_context = plansource->context; MemoryContext oldcxt = CurrentMemoryContext; /* Assert caller is doing things in a sane order */ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(!plansource->is_complete); /* * If caller supplied a querytree_context, reparent it underneath the * CachedPlanSource's context; otherwise, create a suitable context and * copy the querytree_list into it. But no data copying should be done * for one-shot plans; for those, assume the passed querytree_list is * sufficiently long-lived. */ if (plansource->is_oneshot) { querytree_context = CurrentMemoryContext; } else if (querytree_context != NULL) { MemoryContextSetParent(querytree_context, source_context); MemoryContextSwitchTo(querytree_context); } else { /* Again, it's a good bet the querytree_context can be small */ querytree_context = AllocSetContextCreate(source_context, "CachedPlanQuery", ALLOCSET_START_SMALL_SIZES); MemoryContextSwitchTo(querytree_context); querytree_list = copyObject(querytree_list); } plansource->query_context = querytree_context; plansource->query_list = querytree_list; if (!plansource->is_oneshot && StmtPlanRequiresRevalidation(plansource)) { /* * Use the planner machinery to extract dependencies. Data is saved * in query_context. (We assume that not a lot of extra cruft is * created by this call.) We can skip this for one-shot plans, and * plans not needing revalidation have no such dependencies anyway. */ extract_query_dependencies((Node *) querytree_list, &plansource->relationOids, &plansource->invalItems, &plansource->dependsOnRLS); /* Update RLS info as well. */ plansource->rewriteRoleId = GetUserId(); plansource->rewriteRowSecurity = row_security; /* * Also save the current search_path in the query_context. (This * should not generate much extra cruft either, since almost certainly * the path is already valid.) Again, we don't really need this for * one-shot plans; and we *must* skip this for transaction control * commands, because this could result in catalog accesses. */ plansource->search_path = GetSearchPathMatcher(querytree_context); } /* * Save the final parameter types (or other parameter specification data) * into the source_context, as well as our other parameters. Also save * the result tuple descriptor. */ MemoryContextSwitchTo(source_context); if (num_params > 0) { plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid)); memcpy(plansource->param_types, param_types, num_params * sizeof(Oid)); } else plansource->param_types = NULL; plansource->num_params = num_params; plansource->parserSetup = parserSetup; plansource->parserSetupArg = parserSetupArg; plansource->cursor_options = cursor_options; plansource->fixed_result = fixed_result; plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list); MemoryContextSwitchTo(oldcxt); plansource->is_complete = true; plansource->is_valid = true; } /* * SetPostRewriteHook: set a hook to modify post-rewrite query trees * * Some callers have a need to modify the query trees between rewriting and * planning. In the initial call to CompleteCachedPlan, it's assumed such * work was already done on the querytree_list. However, if we're forced * to replan, it will need to be done over. The caller can set this hook * to provide code to make that happen. * * postRewriteArg is just passed verbatim to the hook. As with parserSetupArg, * it is caller's responsibility that the referenced data remains * valid for as long as the CachedPlanSource exists. */ void SetPostRewriteHook(CachedPlanSource *plansource, PostRewriteHook postRewrite, void *postRewriteArg) { Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); plansource->postRewrite = postRewrite; plansource->postRewriteArg = postRewriteArg; } /* * SaveCachedPlan: save a cached plan permanently * * This function moves the cached plan underneath CacheMemoryContext (making * it live for the life of the backend, unless explicitly dropped), and adds * it to the list of cached plans that are checked for invalidation when an * sinval event occurs. * * This is guaranteed not to throw error, except for the caller-error case * of trying to save a one-shot plan. Callers typically depend on that * since this is called just before or just after adding a pointer to the * CachedPlanSource to some permanent data structure of their own. Up until * this is done, a CachedPlanSource is just transient data that will go away * automatically on transaction abort. */ void SaveCachedPlan(CachedPlanSource *plansource) { /* Assert caller is doing things in a sane order */ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plansource->is_complete); Assert(!plansource->is_saved); /* This seems worth a real test, though */ if (plansource->is_oneshot) elog(ERROR, "cannot save one-shot cached plan"); /* * In typical use, this function would be called before generating any * plans from the CachedPlanSource. If there is a generic plan, moving it * into CacheMemoryContext would be pretty risky since it's unclear * whether the caller has taken suitable care with making references * long-lived. Best thing to do seems to be to discard the plan. */ ReleaseGenericPlan(plansource); /* * Reparent the source memory context under CacheMemoryContext so that it * will live indefinitely. The query_context follows along since it's * already a child of the other one. */ MemoryContextSetParent(plansource->context, CacheMemoryContext); /* * Add the entry to the global list of cached plans. */ dlist_push_tail(&saved_plan_list, &plansource->node); plansource->is_saved = true; } /* * DropCachedPlan: destroy a cached plan. * * Actually this only destroys the CachedPlanSource: any referenced CachedPlan * is released, but not destroyed until its refcount goes to zero. That * handles the situation where DropCachedPlan is called while the plan is * still in use. */ void DropCachedPlan(CachedPlanSource *plansource) { Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* If it's been saved, remove it from the list */ if (plansource->is_saved) { dlist_delete(&plansource->node); plansource->is_saved = false; } /* Decrement generic CachedPlan's refcount and drop if no longer needed */ ReleaseGenericPlan(plansource); /* Mark it no longer valid */ plansource->magic = 0; /* * Remove the CachedPlanSource and all subsidiary data (including the * query_context if any). But if it's a one-shot we can't free anything. */ if (!plansource->is_oneshot) MemoryContextDelete(plansource->context); } /* * ReleaseGenericPlan: release a CachedPlanSource's generic plan, if any. */ static void ReleaseGenericPlan(CachedPlanSource *plansource) { /* Be paranoid about the possibility that ReleaseCachedPlan fails */ if (plansource->gplan) { CachedPlan *plan = plansource->gplan; Assert(plan->magic == CACHEDPLAN_MAGIC); plansource->gplan = NULL; ReleaseCachedPlan(plan, NULL); } } /* * We must skip "overhead" operations that involve database access when the * cached plan's subject statement is a transaction control command or one * that requires a snapshot not to be set yet (such as SET or LOCK). More * generally, statements that do not require parse analysis/rewrite/plan * activity never need to be revalidated, so we can treat them all like that. * For the convenience of postgres.c, treat empty statements that way too. */ static bool StmtPlanRequiresRevalidation(CachedPlanSource *plansource) { if (plansource->raw_parse_tree != NULL) return stmt_requires_parse_analysis(plansource->raw_parse_tree); else if (plansource->analyzed_parse_tree != NULL) return query_requires_rewrite_plan(plansource->analyzed_parse_tree); /* empty query never needs revalidation */ return false; } /* * Determine if creating a plan for this CachedPlanSource requires a snapshot. * In fact this function matches StmtPlanRequiresRevalidation(), but we want * to preserve the distinction between stmt_requires_parse_analysis() and * analyze_requires_snapshot(). */ static bool BuildingPlanRequiresSnapshot(CachedPlanSource *plansource) { if (plansource->raw_parse_tree != NULL) return analyze_requires_snapshot(plansource->raw_parse_tree); else if (plansource->analyzed_parse_tree != NULL) return query_requires_rewrite_plan(plansource->analyzed_parse_tree); /* empty query never needs a snapshot */ return false; } /* * RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree. * * What we do here is re-acquire locks and redo parse analysis if necessary. * On return, the query_list is valid and we have sufficient locks to begin * planning. * * If any parse analysis activity is required, the caller's memory context is * used for that work. * * The result value is the transient analyzed-and-rewritten query tree if we * had to do re-analysis, and NIL otherwise. (This is returned just to save * a tree copying step in a subsequent BuildCachedPlan call.) * * This also releases and drops the generic plan (plansource->gplan), if any, * as most callers will typically build a new CachedPlan for the plansource * right after this. However, when called from UpdateCachedPlan(), the * function does not release the generic plan, as UpdateCachedPlan() updates * an existing CachedPlan in place. */ static List * RevalidateCachedQuery(CachedPlanSource *plansource, QueryEnvironment *queryEnv, bool release_generic) { bool snapshot_set; List *tlist; /* transient query-tree list */ List *qlist; /* permanent query-tree list */ TupleDesc resultDesc; MemoryContext querytree_context; MemoryContext oldcxt; /* * For one-shot plans, we do not support revalidation checking; it's * assumed the query is parsed, planned, and executed in one transaction, * so that no lock re-acquisition is necessary. Also, if the statement * type can't require revalidation, we needn't do anything (and we mustn't * risk catalog accesses when handling, eg, transaction control commands). */ if (plansource->is_oneshot || !StmtPlanRequiresRevalidation(plansource)) { Assert(plansource->is_valid); return NIL; } /* * If the query is currently valid, we should have a saved search_path --- * check to see if that matches the current environment. If not, we want * to force replan. (We could almost ignore this consideration when * working from an analyzed parse tree; but there are scenarios where * planning can have search_path-dependent results, for example if it * inlines an old-style SQL function.) */ if (plansource->is_valid) { Assert(plansource->search_path != NULL); if (!SearchPathMatchesCurrentEnvironment(plansource->search_path)) { /* Invalidate the querytree and generic plan */ plansource->is_valid = false; if (plansource->gplan) plansource->gplan->is_valid = false; } } /* * If the query rewrite phase had a possible RLS dependency, we must redo * it if either the role or the row_security setting has changed. */ if (plansource->is_valid && plansource->dependsOnRLS && (plansource->rewriteRoleId != GetUserId() || plansource->rewriteRowSecurity != row_security)) plansource->is_valid = false; /* * If the query is currently valid, acquire locks on the referenced * objects; then check again. We need to do it this way to cover the race * condition that an invalidation message arrives before we get the locks. */ if (plansource->is_valid) { AcquirePlannerLocks(plansource->query_list, true); /* * By now, if any invalidation has happened, the inval callback * functions will have marked the query invalid. */ if (plansource->is_valid) { /* Successfully revalidated and locked the query. */ return NIL; } /* Oops, the race case happened. Release useless locks. */ AcquirePlannerLocks(plansource->query_list, false); } /* * Discard the no-longer-useful rewritten query tree. (Note: we don't * want to do this any earlier, else we'd not have been able to release * locks correctly in the race condition case.) */ plansource->is_valid = false; plansource->query_list = NIL; plansource->relationOids = NIL; plansource->invalItems = NIL; plansource->search_path = NULL; /* * Free the query_context. We don't really expect MemoryContextDelete to * fail, but just in case, make sure the CachedPlanSource is left in a * reasonably sane state. (The generic plan won't get unlinked yet, but * that's acceptable.) */ if (plansource->query_context) { MemoryContext qcxt = plansource->query_context; plansource->query_context = NULL; MemoryContextDelete(qcxt); } /* Drop the generic plan reference, if any, and if requested */ if (release_generic) ReleaseGenericPlan(plansource); /* * Now re-do parse analysis and rewrite. This not incidentally acquires * the locks we need to do planning safely. */ Assert(plansource->is_complete); /* * If a snapshot is already set (the normal case), we can just use that * for parsing/planning. But if it isn't, install one. Note: no point in * checking whether parse analysis requires a snapshot; utility commands * don't have invalidatable plans, so we'd not get here for such a * command. */ snapshot_set = false; if (!ActiveSnapshotSet()) { PushActiveSnapshot(GetTransactionSnapshot()); snapshot_set = true; } /* * Run parse analysis (if needed) and rule rewriting. */ if (plansource->raw_parse_tree != NULL) { /* Source is raw parse tree */ RawStmt *rawtree; /* * The parser tends to scribble on its input, so we must copy the raw * parse tree to prevent corruption of the cache. */ rawtree = copyObject(plansource->raw_parse_tree); if (plansource->parserSetup != NULL) tlist = pg_analyze_and_rewrite_withcb(rawtree, plansource->query_string, plansource->parserSetup, plansource->parserSetupArg, queryEnv); else tlist = pg_analyze_and_rewrite_fixedparams(rawtree, plansource->query_string, plansource->param_types, plansource->num_params, queryEnv); } else if (plansource->analyzed_parse_tree != NULL) { /* Source is pre-analyzed query, so we only need to rewrite */ Query *analyzed_tree; /* The rewriter scribbles on its input, too, so copy */ analyzed_tree = copyObject(plansource->analyzed_parse_tree); /* Acquire locks needed before rewriting ... */ AcquireRewriteLocks(analyzed_tree, true, false); /* ... and do it */ tlist = pg_rewrite_query(analyzed_tree); } else { /* Empty query, nothing to do */ tlist = NIL; } /* Apply post-rewrite callback if there is one */ if (plansource->postRewrite != NULL) plansource->postRewrite(tlist, plansource->postRewriteArg); /* Release snapshot if we got one */ if (snapshot_set) PopActiveSnapshot(); /* * Check or update the result tupdesc. * * We assume the parameter types didn't change from the first time, so no * need to update that. */ resultDesc = PlanCacheComputeResultDesc(tlist); if (resultDesc == NULL && plansource->resultDesc == NULL) { /* OK, doesn't return tuples */ } else if (resultDesc == NULL || plansource->resultDesc == NULL || !equalRowTypes(resultDesc, plansource->resultDesc)) { /* 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); } /* * Allocate new query_context and copy the completed querytree into it. * It's transient until we complete the copying and dependency extraction. */ querytree_context = AllocSetContextCreate(CurrentMemoryContext, "CachedPlanQuery", ALLOCSET_START_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(querytree_context); qlist = copyObject(tlist); /* * Use the planner machinery to extract dependencies. Data is saved in * query_context. (We assume that not a lot of extra cruft is created by * this call.) */ extract_query_dependencies((Node *) qlist, &plansource->relationOids, &plansource->invalItems, &plansource->dependsOnRLS); /* Update RLS info as well. */ plansource->rewriteRoleId = GetUserId(); plansource->rewriteRowSecurity = row_security; /* * Also save the current search_path in the query_context. (This should * not generate much extra cruft either, since almost certainly the path * is already valid.) */ plansource->search_path = GetSearchPathMatcher(querytree_context); MemoryContextSwitchTo(oldcxt); /* Now reparent the finished query_context and save the links */ MemoryContextSetParent(querytree_context, plansource->context); plansource->query_context = querytree_context; plansource->query_list = qlist; /* * Note: we do not reset generic_cost or total_custom_cost, although we * could choose to do so. If the DDL or statistics change that prompted * the invalidation meant a significant change in the cost estimates, it * would be better to reset those variables and start fresh; but often it * doesn't, and we're better retaining our hard-won knowledge about the * relative costs. */ plansource->is_valid = true; /* Return transient copy of querytrees for possible use in planning */ return tlist; } /* * CheckCachedPlan: see if the CachedPlanSource's generic plan is valid. * * Caller must have already called RevalidateCachedQuery to verify that the * querytree is up to date. * * On a "true" return, we have acquired locks on the "unprunableRelids" set * for all plans in plansource->stmt_list. However, the plans are not fully * race-condition-free until the executor acquires locks on the prunable * relations that survive initial runtime pruning during InitPlan(). */ static bool CheckCachedPlan(CachedPlanSource *plansource) { CachedPlan *plan = plansource->gplan; /* Assert that caller checked the querytree */ Assert(plansource->is_valid); /* If there's no generic plan, just say "false" */ if (!plan) return false; Assert(plan->magic == CACHEDPLAN_MAGIC); /* Generic plans are never one-shot */ Assert(!plan->is_oneshot); /* * If plan isn't valid for current role, we can't use it. */ if (plan->is_valid && plan->dependsOnRole && plan->planRoleId != GetUserId()) plan->is_valid = false; /* * If it appears valid, acquire locks and recheck; this is much the same * logic as in RevalidateCachedQuery, but for a plan. */ if (plan->is_valid) { /* * 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); AcquireExecutorLocks(plan->stmt_list, true); /* * If plan was transient, check to see if TransactionXmin has * advanced, and if so invalidate it. */ if (plan->is_valid && TransactionIdIsValid(plan->saved_xmin) && !TransactionIdEquals(plan->saved_xmin, TransactionXmin)) plan->is_valid = false; /* * By now, if any invalidation has happened, the inval callback * functions will have marked the plan invalid. */ if (plan->is_valid) { /* Successfully revalidated and locked the query. */ return true; } /* Oops, the race case happened. Release useless locks. */ AcquireExecutorLocks(plan->stmt_list, false); } /* * Plan has been invalidated, so unlink it from the parent and release it. */ ReleaseGenericPlan(plansource); return false; } /* * BuildCachedPlan: construct a new CachedPlan from a CachedPlanSource. * * qlist should be the result value from a previous RevalidateCachedQuery, * or it can be set to NIL if we need to re-copy the plansource's query_list. * * To build a generic, parameter-value-independent plan, pass NULL for * boundParams. To build a custom plan, pass the actual parameter values via * boundParams. For best effect, the PARAM_FLAG_CONST flag should be set on * each parameter value; otherwise the planner will treat the value as a * hint rather than a hard constant. * * Planning work is done in the caller's memory context. The finished plan * is in a child memory context, which typically should get reparented * (unless this is a one-shot plan, in which case we don't copy the plan). * * Note: When changing this, you should also look at UpdateCachedPlan(). */ static CachedPlan * BuildCachedPlan(CachedPlanSource *plansource, List *qlist, ParamListInfo boundParams, QueryEnvironment *queryEnv) { CachedPlan *plan; List *plist; bool snapshot_set; bool is_transient; MemoryContext plan_context; MemoryContext stmt_context = NULL; MemoryContext oldcxt = CurrentMemoryContext; ListCell *lc; /* * Normally the querytree should be valid already, but if it's not, * rebuild it. * * NOTE: GetCachedPlan should have called RevalidateCachedQuery first, so * we ought to be holding sufficient locks to prevent any invalidation. * However, if we're building a custom plan after having built and * rejected a generic plan, it's possible to reach here with is_valid * false due to an invalidation while making the generic plan. In theory * the invalidation must be a false positive, perhaps a consequence of an * sinval reset event or the debug_discard_caches code. But for safety, * let's treat it as real and redo the RevalidateCachedQuery call. */ if (!plansource->is_valid) qlist = RevalidateCachedQuery(plansource, queryEnv, true); /* * If we don't already have a copy of the querytree list that can be * scribbled on by the planner, make one. For a one-shot plan, we assume * it's okay to scribble on the original query_list. */ if (qlist == NIL) { if (!plansource->is_oneshot) qlist = copyObject(plansource->query_list); else qlist = plansource->query_list; } /* * If a snapshot is already set (the normal case), we can just use that * for planning. But if it isn't, and we need one, install one. */ snapshot_set = false; if (!ActiveSnapshotSet() && BuildingPlanRequiresSnapshot(plansource)) { PushActiveSnapshot(GetTransactionSnapshot()); snapshot_set = true; } /* * Generate the plan. */ plist = pg_plan_queries(qlist, plansource->query_string, plansource->cursor_options, boundParams); /* Release snapshot if we got one */ if (snapshot_set) PopActiveSnapshot(); /* * Normally, we create a dedicated memory context for the CachedPlan and * its subsidiary data. Although it's usually not very large, the context * is designed to allow growth if necessary. * * The PlannedStmts are stored in a separate child context (stmt_context) * of the CachedPlan's memory context. This separation allows * UpdateCachedPlan() to free and replace the PlannedStmts without * affecting the CachedPlan structure or its stmt_list List. * * For one-shot plans, we instead use the caller's memory context, as the * CachedPlan will not persist. stmt_context will be set to NULL in this * case, because UpdateCachedPlan() should never get called on a one-shot * plan. */ if (!plansource->is_oneshot) { plan_context = AllocSetContextCreate(CurrentMemoryContext, "CachedPlan", ALLOCSET_START_SMALL_SIZES); MemoryContextCopyAndSetIdentifier(plan_context, plansource->query_string); stmt_context = AllocSetContextCreate(CurrentMemoryContext, "CachedPlan PlannedStmts", ALLOCSET_START_SMALL_SIZES); MemoryContextCopyAndSetIdentifier(stmt_context, plansource->query_string); MemoryContextSetParent(stmt_context, plan_context); MemoryContextSwitchTo(stmt_context); plist = copyObject(plist); MemoryContextSwitchTo(plan_context); plist = list_copy(plist); } else plan_context = CurrentMemoryContext; /* * Create and fill the CachedPlan struct within the new context. */ plan = (CachedPlan *) palloc(sizeof(CachedPlan)); plan->magic = CACHEDPLAN_MAGIC; plan->stmt_list = plist; /* * CachedPlan is dependent on role either if RLS affected the rewrite * phase or if a role dependency was injected during planning. And it's * transient if any plan is marked so. */ plan->planRoleId = GetUserId(); plan->dependsOnRole = plansource->dependsOnRLS; is_transient = false; foreach(lc, plist) { PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); if (plannedstmt->commandType == CMD_UTILITY) continue; /* Ignore utility statements */ if (plannedstmt->transientPlan) is_transient = true; if (plannedstmt->dependsOnRole) plan->dependsOnRole = true; } if (is_transient) { Assert(TransactionIdIsNormal(TransactionXmin)); plan->saved_xmin = TransactionXmin; } else plan->saved_xmin = InvalidTransactionId; plan->refcount = 0; plan->context = plan_context; plan->stmt_context = stmt_context; plan->is_oneshot = plansource->is_oneshot; plan->is_saved = false; plan->is_reused = false; plan->is_valid = true; /* assign generation number to new plan */ plan->generation = ++(plansource->generation); MemoryContextSwitchTo(oldcxt); return plan; } /* * UpdateCachedPlan * Create fresh plans for all queries in the CachedPlanSource, replacing * those in the generic plan's stmt_list, and return the plan for the * query_index'th query. * * This function is primarily used by ExecutorStartCachedPlan() to handle * cases where the original generic CachedPlan becomes invalid. Such * invalidation may occur when prunable relations in the old plan for the * query_index'th query are locked in preparation for execution. * * Note that invalidations received during the execution of the query_index'th * query can affect both the queries that have already finished execution * (e.g., due to concurrent modifications on prunable relations that were not * locked during their execution) and also the queries that have not yet been * executed. As a result, this function updates all plans to ensure * CachedPlan.is_valid is safely set to true. * * The old PlannedStmts in plansource->gplan->stmt_list are freed here, so * the caller and any of its callers must not rely on them remaining accessible * after this function is called. */ PlannedStmt * UpdateCachedPlan(CachedPlanSource *plansource, int query_index, QueryEnvironment *queryEnv) { List *query_list = plansource->query_list, *plan_list; ListCell *l1, *l2; CachedPlan *plan = plansource->gplan; MemoryContext oldcxt; Assert(ActiveSnapshotSet()); /* Sanity checks (XXX can be Asserts?) */ if (plan == NULL) elog(ERROR, "UpdateCachedPlan() called in the wrong context: plansource->gplan is NULL"); else if (plan->is_valid) elog(ERROR, "UpdateCachedPlan() called in the wrong context: plansource->gplan->is_valid is true"); else if (plan->is_oneshot) elog(ERROR, "UpdateCachedPlan() called in the wrong context: plansource->gplan->is_oneshot is true"); /* * The plansource might have become invalid since GetCachedPlan() returned * the CachedPlan. See the comment in BuildCachedPlan() for details on why * this might happen. Although invalidation is likely a false positive as * stated there, we make the plan valid to ensure the query list used for * planning is up to date. * * The risk of catching an invalidation is higher here than when * BuildCachedPlan() is called from GetCachedPlan(), because this function * is normally called long after GetCachedPlan() returns the CachedPlan, * so much more processing could have occurred including things that mark * the CachedPlanSource invalid. * * Note: Do not release plansource->gplan, because the upstream callers * (such as the callers of ExecutorStartCachedPlan()) would still be * referencing it. */ if (!plansource->is_valid) query_list = RevalidateCachedQuery(plansource, queryEnv, false); Assert(query_list != NIL); /* * Build a new generic plan for all the queries after making a copy to be * scribbled on by the planner. */ query_list = copyObject(query_list); /* * Planning work is done in the caller's memory context. The resulting * PlannedStmt is then copied into plan->stmt_context after throwing away * the old ones. */ plan_list = pg_plan_queries(query_list, plansource->query_string, plansource->cursor_options, NULL); Assert(list_length(plan_list) == list_length(plan->stmt_list)); MemoryContextReset(plan->stmt_context); oldcxt = MemoryContextSwitchTo(plan->stmt_context); forboth(l1, plan_list, l2, plan->stmt_list) { PlannedStmt *plannedstmt = lfirst(l1); lfirst(l2) = copyObject(plannedstmt); } MemoryContextSwitchTo(oldcxt); /* * XXX Should this also (re)set the properties of the CachedPlan that are * set in BuildCachedPlan() after creating the fresh plans such as * planRoleId, dependsOnRole, and saved_xmin? */ /* * We've updated all the plans that might have been invalidated, so mark * the CachedPlan as valid. */ plan->is_valid = true; /* Also update generic_cost because we just created a new generic plan. */ plansource->generic_cost = cached_plan_cost(plan, false); return list_nth_node(PlannedStmt, plan->stmt_list, query_index); } /* * choose_custom_plan: choose whether to use custom or generic plan * * This defines the policy followed by GetCachedPlan. */ static bool choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams) { double avg_custom_cost; /* One-shot plans will always be considered custom */ if (plansource->is_oneshot) return true; /* Otherwise, never any point in a custom plan if there's no parameters */ if (boundParams == NULL) return false; /* ... nor when planning would be a no-op */ if (!StmtPlanRequiresRevalidation(plansource)) return false; /* Let settings force the decision */ if (plan_cache_mode == PLAN_CACHE_MODE_FORCE_GENERIC_PLAN) return false; if (plan_cache_mode == PLAN_CACHE_MODE_FORCE_CUSTOM_PLAN) return true; /* See if caller wants to force the decision */ if (plansource->cursor_options & CURSOR_OPT_GENERIC_PLAN) return false; if (plansource->cursor_options & CURSOR_OPT_CUSTOM_PLAN) return true; /* Generate custom plans until we have done at least 5 (arbitrary) */ if (plansource->num_custom_plans < 5) return true; avg_custom_cost = plansource->total_custom_cost / plansource->num_custom_plans; /* * Prefer generic plan if it's less expensive than the average custom * plan. (Because we include a charge for cost of planning in the * custom-plan costs, this means the generic plan only has to be less * expensive than the execution cost plus replan cost of the custom * plans.) * * Note that if generic_cost is -1 (indicating we've not yet determined * the generic plan cost), we'll always prefer generic at this point. */ if (plansource->generic_cost < avg_custom_cost) return false; return true; } /* * cached_plan_cost: calculate estimated cost of a plan * * If include_planner is true, also include the estimated cost of constructing * the plan. (We must factor that into the cost of using a custom plan, but * we don't count it for a generic plan.) */ static double cached_plan_cost(CachedPlan *plan, bool include_planner) { double result = 0; ListCell *lc; foreach(lc, plan->stmt_list) { PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); if (plannedstmt->commandType == CMD_UTILITY) continue; /* Ignore utility statements */ result += plannedstmt->planTree->total_cost; if (include_planner) { /* * Currently we use a very crude estimate of planning effort based * on the number of relations in the finished plan's rangetable. * Join planning effort actually scales much worse than linearly * in the number of relations --- but only until the join collapse * limits kick in. Also, while inheritance child relations surely * add to planning effort, they don't make the join situation * worse. So the actual shape of the planning cost curve versus * number of relations isn't all that obvious. It will take * considerable work to arrive at a less crude estimate, and for * now it's not clear that's worth doing. * * The other big difficulty here is that we don't have any very * good model of how planning cost compares to execution costs. * The current multiplier of 1000 * cpu_operator_cost is probably * on the low side, but we'll try this for awhile before making a * more aggressive correction. * * If we ever do write a more complicated estimator, it should * probably live in src/backend/optimizer/ not here. */ int nrelations = list_length(plannedstmt->rtable); result += 1000.0 * cpu_operator_cost * (nrelations + 1); } } return result; } /* * GetCachedPlan: get a cached plan from a CachedPlanSource. * * This function hides the logic that decides whether to use a generic * plan or a custom plan for the given parameters: the caller does not know * which it will get. * * On return, the plan is valid, but if it is a reused generic plan, not all * locks are acquired. In such cases, CheckCachedPlan() does not take locks * on relations subject to initial runtime pruning; instead, these locks are * deferred until execution startup, when ExecDoInitialPruning() performs * initial pruning. The plan's "is_reused" flag is set to indicate that * CachedPlanRequiresLocking() should return true when called by * ExecDoInitialPruning(). * * On return, the refcount of the plan has been incremented; a later * ReleaseCachedPlan() call is expected. If "owner" is not NULL then * the refcount has been reported to that ResourceOwner (note that this * is only supported for "saved" CachedPlanSources). * * Note: if any replanning activity is required, the caller's memory context * is used for that work. */ CachedPlan * GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams, ResourceOwner owner, QueryEnvironment *queryEnv) { CachedPlan *plan = NULL; List *qlist; bool customplan; /* Assert caller is doing things in a sane order */ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plansource->is_complete); /* This seems worth a real test, though */ if (owner && !plansource->is_saved) elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan"); /* Make sure the querytree list is valid and we have parse-time locks */ qlist = RevalidateCachedQuery(plansource, queryEnv, true); /* Decide whether to use a custom plan */ customplan = choose_custom_plan(plansource, boundParams); if (!customplan) { if (CheckCachedPlan(plansource)) { /* We want a generic plan, and we already have a valid one */ plan = plansource->gplan; Assert(plan->magic == CACHEDPLAN_MAGIC); /* Reusing the existing plan, so not all locks may be acquired. */ plan->is_reused = true; } else { /* Build a new generic plan */ plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv); /* Just make real sure plansource->gplan is clear */ ReleaseGenericPlan(plansource); /* Link the new generic plan into the plansource */ plansource->gplan = plan; plan->refcount++; /* Immediately reparent into appropriate context */ if (plansource->is_saved) { /* saved plans all live under CacheMemoryContext */ MemoryContextSetParent(plan->context, CacheMemoryContext); plan->is_saved = true; } else { /* otherwise, it should be a sibling of the plansource */ MemoryContextSetParent(plan->context, MemoryContextGetParent(plansource->context)); } /* Update generic_cost whenever we make a new generic plan */ plansource->generic_cost = cached_plan_cost(plan, false); /* * If, based on the now-known value of generic_cost, we'd not have * chosen to use a generic plan, then forget it and make a custom * plan. This is a bit of a wart but is necessary to avoid a * glitch in behavior when the custom plans are consistently big * winners; at some point we'll experiment with a generic plan and * find it's a loser, but we don't want to actually execute that * plan. */ customplan = choose_custom_plan(plansource, boundParams); /* * If we choose to plan again, we need to re-copy the query_list, * since the planner probably scribbled on it. We can force * BuildCachedPlan to do that by passing NIL. */ qlist = NIL; } } if (customplan) { /* Build a custom plan */ plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv); /* Accumulate total costs of custom plans */ plansource->total_custom_cost += cached_plan_cost(plan, true); plansource->num_custom_plans++; } else { plansource->num_generic_plans++; } Assert(plan != NULL); /* Flag the plan as in use by caller */ if (owner) ResourceOwnerEnlarge(owner); plan->refcount++; if (owner) ResourceOwnerRememberPlanCacheRef(owner, plan); /* * Saved plans should be under CacheMemoryContext so they will not go away * until their reference count goes to zero. In the generic-plan cases we * already took care of that, but for a custom plan, do it as soon as we * have created a reference-counted link. */ if (customplan && plansource->is_saved) { MemoryContextSetParent(plan->context, CacheMemoryContext); plan->is_saved = true; } return plan; } /* * 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 "owner" is not NULL, it is assumed that * the reference count is managed by that ResourceOwner. * * Note: owner == NULL 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, ResourceOwner owner) { Assert(plan->magic == CACHEDPLAN_MAGIC); if (owner) { Assert(plan->is_saved); ResourceOwnerForgetPlanCacheRef(owner, plan); } Assert(plan->refcount > 0); plan->refcount--; if (plan->refcount == 0) { /* Mark it no longer valid */ plan->magic = 0; /* One-shot plans do not own their context, so we can't free them */ if (!plan->is_oneshot) MemoryContextDelete(plan->context); } } /* * CachedPlanAllowsSimpleValidityCheck: can we use CachedPlanIsSimplyValid? * * This function, together with CachedPlanIsSimplyValid, provides a fast path * for revalidating "simple" generic plans. The core requirement to be simple * is that the plan must not require taking any locks, which translates to * not touching any tables; this happens to match up well with an important * use-case in PL/pgSQL. This function tests whether that's true, along * with checking some other corner cases that we'd rather not bother with * handling in the fast path. (Note that it's still possible for such a plan * to be invalidated, for example due to a change in a function that was * inlined into the plan.) * * If the plan is simply valid, and "owner" is not NULL, record a refcount on * the plan in that resowner before returning. It is caller's responsibility * to be sure that a refcount is held on any plan that's being actively used. * * This must only be called on known-valid generic plans (eg, ones just * returned by GetCachedPlan). If it returns true, the caller may re-use * the cached plan as long as CachedPlanIsSimplyValid returns true; that * check is much cheaper than the full revalidation done by GetCachedPlan. * Nonetheless, no required checks are omitted. */ bool CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource, CachedPlan *plan, ResourceOwner owner) { ListCell *lc; /* * Sanity-check that the caller gave us a validated generic plan. Notice * that we *don't* assert plansource->is_valid as you might expect; that's * because it's possible that that's already false when GetCachedPlan * returns, e.g. because ResetPlanCache happened partway through. We * should accept the plan as long as plan->is_valid is true, and expect to * replan after the next CachedPlanIsSimplyValid call. */ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plan->magic == CACHEDPLAN_MAGIC); Assert(plan->is_valid); Assert(plan == plansource->gplan); Assert(plansource->search_path != NULL); Assert(SearchPathMatchesCurrentEnvironment(plansource->search_path)); /* We don't support oneshot plans here. */ if (plansource->is_oneshot) return false; Assert(!plan->is_oneshot); /* * If the plan is dependent on RLS considerations, or it's transient, * reject. These things probably can't ever happen for table-free * queries, but for safety's sake let's check. */ if (plansource->dependsOnRLS) return false; if (plan->dependsOnRole) return false; if (TransactionIdIsValid(plan->saved_xmin)) return false; /* * Reject if AcquirePlannerLocks would have anything to do. This is * simplistic, but there's no need to inquire any more carefully; indeed, * for current callers it shouldn't even be possible to hit any of these * checks. */ foreach(lc, plansource->query_list) { Query *query = lfirst_node(Query, lc); if (query->commandType == CMD_UTILITY) return false; if (query->rtable || query->cteList || query->hasSubLinks) return false; } /* * Reject if AcquireExecutorLocks would have anything to do. This is * probably unnecessary given the previous check, but let's be safe. */ foreach(lc, plan->stmt_list) { PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); ListCell *lc2; if (plannedstmt->commandType == CMD_UTILITY) return false; /* * We have to grovel through the rtable because it's likely to contain * an RTE_RESULT relation, rather than being totally empty. */ foreach(lc2, plannedstmt->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2); if (rte->rtekind == RTE_RELATION) return false; } } /* * Okay, it's simple. Note that what we've primarily established here is * that no locks need be taken before checking the plan's is_valid flag. */ /* Bump refcount if requested. */ if (owner) { ResourceOwnerEnlarge(owner); plan->refcount++; ResourceOwnerRememberPlanCacheRef(owner, plan); } return true; } /* * CachedPlanIsSimplyValid: quick check for plan still being valid * * This function must not be used unless CachedPlanAllowsSimpleValidityCheck * previously said it was OK. * * If the plan is valid, and "owner" is not NULL, record a refcount on * the plan in that resowner before returning. It is caller's responsibility * to be sure that a refcount is held on any plan that's being actively used. * * The code here is unconditionally safe as long as the only use of this * CachedPlanSource is in connection with the particular CachedPlan pointer * that's passed in. If the plansource were being used for other purposes, * it's possible that its generic plan could be invalidated and regenerated * while the current caller wasn't looking, and then there could be a chance * collision of address between this caller's now-stale plan pointer and the * actual address of the new generic plan. For current uses, that scenario * can't happen; but with a plansource shared across multiple uses, it'd be * advisable to also save plan->generation and verify that that still matches. */ bool CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan, ResourceOwner owner) { /* * Careful here: since the caller doesn't necessarily hold a refcount on * the plan to start with, it's possible that "plan" is a dangling * pointer. Don't dereference it until we've verified that it still * matches the plansource's gplan (which is either valid or NULL). */ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* * Has cache invalidation fired on this plan? We can check this right * away since there are no locks that we'd need to acquire first. Note * that here we *do* check plansource->is_valid, so as to force plan * rebuild if that's become false. */ if (!plansource->is_valid || plan == NULL || plan != plansource->gplan || !plan->is_valid) return false; Assert(plan->magic == CACHEDPLAN_MAGIC); /* Is the search_path still the same as when we made it? */ Assert(plansource->search_path != NULL); if (!SearchPathMatchesCurrentEnvironment(plansource->search_path)) return false; /* It's still good. Bump refcount if requested. */ if (owner) { ResourceOwnerEnlarge(owner); plan->refcount++; ResourceOwnerRememberPlanCacheRef(owner, plan); } return true; } /* * CachedPlanSetParentContext: move a CachedPlanSource to a new memory context * * This can only be applied to unsaved plans; once saved, a plan always * lives underneath CacheMemoryContext. */ void CachedPlanSetParentContext(CachedPlanSource *plansource, MemoryContext newcontext) { /* Assert caller is doing things in a sane order */ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plansource->is_complete); /* These seem worth real tests, though */ if (plansource->is_saved) elog(ERROR, "cannot move a saved cached plan to another context"); if (plansource->is_oneshot) elog(ERROR, "cannot move a one-shot cached plan to another context"); /* OK, let the caller keep the plan where he wishes */ MemoryContextSetParent(plansource->context, newcontext); /* * The query_context needs no special handling, since it's a child of * plansource->context. But if there's a generic plan, it should be * maintained as a sibling of plansource->context. */ if (plansource->gplan) { Assert(plansource->gplan->magic == CACHEDPLAN_MAGIC); MemoryContextSetParent(plansource->gplan->context, newcontext); } } /* * CopyCachedPlan: make a copy of a CachedPlanSource * * This is a convenience routine that does the equivalent of * CreateCachedPlan + CompleteCachedPlan, using the data stored in the * input CachedPlanSource. The result is therefore "unsaved" (regardless * of the state of the source), and we don't copy any generic plan either. * The result will be currently valid, or not, the same as the source. */ CachedPlanSource * CopyCachedPlan(CachedPlanSource *plansource) { CachedPlanSource *newsource; MemoryContext source_context; MemoryContext querytree_context; MemoryContext oldcxt; Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plansource->is_complete); /* * One-shot plans can't be copied, because we haven't taken care that * parsing/planning didn't scribble on the raw parse tree or querytrees. */ if (plansource->is_oneshot) elog(ERROR, "cannot copy a one-shot cached plan"); source_context = AllocSetContextCreate(CurrentMemoryContext, "CachedPlanSource", ALLOCSET_START_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(source_context); newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); newsource->magic = CACHEDPLANSOURCE_MAGIC; newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree); newsource->analyzed_parse_tree = copyObject(plansource->analyzed_parse_tree); newsource->query_string = pstrdup(plansource->query_string); MemoryContextSetIdentifier(source_context, newsource->query_string); newsource->commandTag = plansource->commandTag; if (plansource->num_params > 0) { newsource->param_types = (Oid *) palloc(plansource->num_params * sizeof(Oid)); memcpy(newsource->param_types, plansource->param_types, plansource->num_params * sizeof(Oid)); } else newsource->param_types = NULL; newsource->num_params = plansource->num_params; newsource->parserSetup = plansource->parserSetup; newsource->parserSetupArg = plansource->parserSetupArg; newsource->postRewrite = plansource->postRewrite; newsource->postRewriteArg = plansource->postRewriteArg; newsource->cursor_options = plansource->cursor_options; newsource->fixed_result = plansource->fixed_result; if (plansource->resultDesc) newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc); else newsource->resultDesc = NULL; newsource->context = source_context; querytree_context = AllocSetContextCreate(source_context, "CachedPlanQuery", ALLOCSET_START_SMALL_SIZES); MemoryContextSwitchTo(querytree_context); newsource->query_list = copyObject(plansource->query_list); newsource->relationOids = copyObject(plansource->relationOids); newsource->invalItems = copyObject(plansource->invalItems); if (plansource->search_path) newsource->search_path = CopySearchPathMatcher(plansource->search_path); newsource->query_context = querytree_context; newsource->rewriteRoleId = plansource->rewriteRoleId; newsource->rewriteRowSecurity = plansource->rewriteRowSecurity; newsource->dependsOnRLS = plansource->dependsOnRLS; newsource->gplan = NULL; newsource->is_oneshot = false; newsource->is_complete = true; newsource->is_saved = false; newsource->is_valid = plansource->is_valid; newsource->generation = plansource->generation; /* We may as well copy any acquired cost knowledge */ newsource->generic_cost = plansource->generic_cost; newsource->total_custom_cost = plansource->total_custom_cost; newsource->num_generic_plans = plansource->num_generic_plans; newsource->num_custom_plans = plansource->num_custom_plans; MemoryContextSwitchTo(oldcxt); return newsource; } /* * CachedPlanIsValid: test whether the rewritten querytree within a * CachedPlanSource is currently valid (that is, not marked as being in need * of revalidation). * * This result is only trustworthy (ie, free from race conditions) if * the caller has acquired locks on all the relations used in the plan. */ bool CachedPlanIsValid(CachedPlanSource *plansource) { Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); return plansource->is_valid; } /* * CachedPlanGetTargetList: return tlist, if any, describing plan's output * * The result is guaranteed up-to-date. However, it is local storage * within the cached plan, and may disappear next time the plan is updated. */ List * CachedPlanGetTargetList(CachedPlanSource *plansource, QueryEnvironment *queryEnv) { Query *pstmt; /* Assert caller is doing things in a sane order */ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(plansource->is_complete); /* * No work needed if statement doesn't return tuples (we assume this * feature cannot be changed by an invalidation) */ if (plansource->resultDesc == NULL) return NIL; /* Make sure the querytree list is valid and we have parse-time locks */ RevalidateCachedQuery(plansource, queryEnv, true); /* Get the primary statement and find out what it returns */ pstmt = QueryListGetPrimaryStmt(plansource->query_list); return FetchStatementTargetList((Node *) pstmt); } /* * GetCachedExpression: construct a CachedExpression for an expression. * * This performs the same transformations on the expression as * expression_planner(), ie, convert an expression as emitted by parse * analysis to be ready to pass to the executor. * * The result is stashed in a private, long-lived memory context. * (Note that this might leak a good deal of memory in the caller's * context before that.) The passed-in expr tree is not modified. */ CachedExpression * GetCachedExpression(Node *expr) { CachedExpression *cexpr; List *relationOids; List *invalItems; MemoryContext cexpr_context; MemoryContext oldcxt; /* * Pass the expression through the planner, and collect dependencies. * Everything built here is leaked in the caller's context; that's * intentional to minimize the size of the permanent data structure. */ expr = (Node *) expression_planner_with_deps((Expr *) expr, &relationOids, &invalItems); /* * Make a private memory context, and copy what we need into that. To * avoid leaking a long-lived context if we fail while copying data, we * initially make the context under the caller's context. */ cexpr_context = AllocSetContextCreate(CurrentMemoryContext, "CachedExpression", ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(cexpr_context); cexpr = (CachedExpression *) palloc(sizeof(CachedExpression)); cexpr->magic = CACHEDEXPR_MAGIC; cexpr->expr = copyObject(expr); cexpr->is_valid = true; cexpr->relationOids = copyObject(relationOids); cexpr->invalItems = copyObject(invalItems); cexpr->context = cexpr_context; MemoryContextSwitchTo(oldcxt); /* * Reparent the expr's memory context under CacheMemoryContext so that it * will live indefinitely. */ MemoryContextSetParent(cexpr_context, CacheMemoryContext); /* * Add the entry to the global list of cached expressions. */ dlist_push_tail(&cached_expression_list, &cexpr->node); return cexpr; } /* * FreeCachedExpression * Delete a CachedExpression. */ void FreeCachedExpression(CachedExpression *cexpr) { /* Sanity check */ Assert(cexpr->magic == CACHEDEXPR_MAGIC); /* Unlink from global list */ dlist_delete(&cexpr->node); /* Free all storage associated with CachedExpression */ MemoryContextDelete(cexpr->context); } /* * QueryListGetPrimaryStmt * Get the "primary" stmt within a list, ie, the one marked canSetTag. * * Returns NULL if no such stmt. If multiple queries within the list are * marked canSetTag, returns the first one. Neither of these cases should * occur in present usages of this function. */ static Query * QueryListGetPrimaryStmt(List *stmts) { ListCell *lc; foreach(lc, stmts) { Query *stmt = lfirst_node(Query, lc); if (stmt->canSetTag) return stmt; } return NULL; } /* * AcquireExecutorLocks: acquire locks needed for execution of a cached plan; * or release them if acquire is false. */ static void AcquireExecutorLocks(List *stmt_list, bool acquire) { ListCell *lc1; foreach(lc1, stmt_list) { PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc1); int rtindex; if (plannedstmt->commandType == CMD_UTILITY) { /* * Ignore utility statements, except those (such as EXPLAIN) that * contain 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. */ Query *query = UtilityContainsQuery(plannedstmt->utilityStmt); if (query) ScanQueryForLocks(query, acquire); continue; } rtindex = -1; while ((rtindex = bms_next_member(plannedstmt->unprunableRelids, rtindex)) >= 0) { RangeTblEntry *rte = list_nth_node(RangeTblEntry, plannedstmt->rtable, rtindex - 1); Assert(rte->rtekind == RTE_RELATION || (rte->rtekind == RTE_SUBQUERY && OidIsValid(rte->relid))); /* * 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 (acquire) LockRelationOid(rte->relid, rte->rellockmode); else UnlockRelationOid(rte->relid, rte->rellockmode); } } } /* * AcquirePlannerLocks: acquire locks needed for planning of a querytree list; * or release them if acquire is false. * * Note that we don't actually try to open the relations, and hence will not * fail if one has been dropped entirely --- we'll just transiently acquire * a non-conflicting lock. */ static void AcquirePlannerLocks(List *stmt_list, bool acquire) { ListCell *lc; foreach(lc, stmt_list) { Query *query = lfirst_node(Query, lc); if (query->commandType == CMD_UTILITY) { /* Ignore utility statements, unless they contain a Query */ query = UtilityContainsQuery(query->utilityStmt); if (query) ScanQueryForLocks(query, acquire); continue; } ScanQueryForLocks(query, acquire); } } /* * ScanQueryForLocks: recursively scan one Query for AcquirePlannerLocks. */ static void ScanQueryForLocks(Query *parsetree, bool acquire) { ListCell *lc; /* Shouldn't get called on utility commands */ Assert(parsetree->commandType != CMD_UTILITY); /* * First, process RTEs of the current query level. */ foreach(lc, parsetree->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); switch (rte->rtekind) { case RTE_RELATION: /* Acquire or release the appropriate type of lock */ if (acquire) LockRelationOid(rte->relid, rte->rellockmode); else UnlockRelationOid(rte->relid, rte->rellockmode); break; case RTE_SUBQUERY: /* If this was a view, must lock/unlock the view */ if (OidIsValid(rte->relid)) { if (acquire) LockRelationOid(rte->relid, rte->rellockmode); else UnlockRelationOid(rte->relid, rte->rellockmode); } /* 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 = lfirst_node(CommonTableExpr, lc); ScanQueryForLocks(castNode(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, &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(castNode(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, acquire); } /* * PlanCacheComputeResultDesc: given a list of analyzed-and-rewritten Queries, * determine the result tupledesc it will produce. Returns NULL if the * execution will not return tuples. * * Note: the result is created or copied into current memory context. */ static TupleDesc PlanCacheComputeResultDesc(List *stmt_list) { Query *query; switch (ChoosePortalStrategy(stmt_list)) { case PORTAL_ONE_SELECT: case PORTAL_ONE_MOD_WITH: query = linitial_node(Query, stmt_list); return ExecCleanTypeFromTL(query->targetList); case PORTAL_ONE_RETURNING: query = QueryListGetPrimaryStmt(stmt_list); Assert(query->returningList); return ExecCleanTypeFromTL(query->returningList); case PORTAL_UTIL_SELECT: query = linitial_node(Query, stmt_list); Assert(query->utilityStmt); return UtilityTupleDescriptor(query->utilityStmt); 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) { dlist_iter iter; dlist_foreach(iter, &saved_plan_list) { CachedPlanSource *plansource = dlist_container(CachedPlanSource, node, iter.cur); Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* No work if it's already invalidated */ if (!plansource->is_valid) continue; /* Never invalidate if parse/plan would be a no-op anyway */ if (!StmtPlanRequiresRevalidation(plansource)) continue; /* * Check the dependency list for the rewritten querytree. */ if ((relid == InvalidOid) ? plansource->relationOids != NIL : list_member_oid(plansource->relationOids, relid)) { /* Invalidate the querytree and generic plan */ plansource->is_valid = false; if (plansource->gplan) plansource->gplan->is_valid = false; } /* * The generic plan, if any, could have more dependencies than the * querytree does, so we have to check it too. */ if (plansource->gplan && plansource->gplan->is_valid) { ListCell *lc; foreach(lc, plansource->gplan->stmt_list) { PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); if (plannedstmt->commandType == CMD_UTILITY) continue; /* Ignore utility statements */ if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL : list_member_oid(plannedstmt->relationOids, relid)) { /* Invalidate the generic plan only */ plansource->gplan->is_valid = false; break; /* out of stmt_list scan */ } } } } /* Likewise check cached expressions */ dlist_foreach(iter, &cached_expression_list) { CachedExpression *cexpr = dlist_container(CachedExpression, node, iter.cur); Assert(cexpr->magic == CACHEDEXPR_MAGIC); /* No work if it's already invalidated */ if (!cexpr->is_valid) continue; if ((relid == InvalidOid) ? cexpr->relationOids != NIL : list_member_oid(cexpr->relationOids, relid)) { cexpr->is_valid = false; } } } /* * PlanCacheObjectCallback * Syscache inval callback function for PROCOID and TYPEOID caches * * Invalidate all plans mentioning the object with the specified hash value, * or all plans mentioning any member of this cache if hashvalue == 0. */ static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue) { dlist_iter iter; dlist_foreach(iter, &saved_plan_list) { CachedPlanSource *plansource = dlist_container(CachedPlanSource, node, iter.cur); ListCell *lc; Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* No work if it's already invalidated */ if (!plansource->is_valid) continue; /* Never invalidate if parse/plan would be a no-op anyway */ if (!StmtPlanRequiresRevalidation(plansource)) continue; /* * Check the dependency list for the rewritten querytree. */ foreach(lc, plansource->invalItems) { PlanInvalItem *item = (PlanInvalItem *) lfirst(lc); if (item->cacheId != cacheid) continue; if (hashvalue == 0 || item->hashValue == hashvalue) { /* Invalidate the querytree and generic plan */ plansource->is_valid = false; if (plansource->gplan) plansource->gplan->is_valid = false; break; } } /* * The generic plan, if any, could have more dependencies than the * querytree does, so we have to check it too. */ if (plansource->gplan && plansource->gplan->is_valid) { foreach(lc, plansource->gplan->stmt_list) { PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); ListCell *lc3; if (plannedstmt->commandType == CMD_UTILITY) continue; /* Ignore utility statements */ foreach(lc3, plannedstmt->invalItems) { PlanInvalItem *item = (PlanInvalItem *) lfirst(lc3); if (item->cacheId != cacheid) continue; if (hashvalue == 0 || item->hashValue == hashvalue) { /* Invalidate the generic plan only */ plansource->gplan->is_valid = false; break; /* out of invalItems scan */ } } if (!plansource->gplan->is_valid) break; /* out of stmt_list scan */ } } } /* Likewise check cached expressions */ dlist_foreach(iter, &cached_expression_list) { CachedExpression *cexpr = dlist_container(CachedExpression, node, iter.cur); ListCell *lc; Assert(cexpr->magic == CACHEDEXPR_MAGIC); /* No work if it's already invalidated */ if (!cexpr->is_valid) continue; foreach(lc, cexpr->invalItems) { PlanInvalItem *item = (PlanInvalItem *) lfirst(lc); if (item->cacheId != cacheid) continue; if (hashvalue == 0 || item->hashValue == hashvalue) { cexpr->is_valid = false; break; } } } } /* * PlanCacheSysCallback * Syscache inval callback function for other caches * * Just invalidate everything... */ static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue) { ResetPlanCache(); } /* * ResetPlanCache: invalidate all cached plans. */ void ResetPlanCache(void) { dlist_iter iter; dlist_foreach(iter, &saved_plan_list) { CachedPlanSource *plansource = dlist_container(CachedPlanSource, node, iter.cur); Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* No work if it's already invalidated */ if (!plansource->is_valid) continue; /* * We *must not* mark transaction control statements as invalid, * particularly not ROLLBACK, because they may need to be executed in * aborted transactions when we can't revalidate them (cf bug #5269). * In general there's no point in invalidating statements for which a * new parse analysis/rewrite/plan cycle would certainly give the same * results. */ if (!StmtPlanRequiresRevalidation(plansource)) continue; plansource->is_valid = false; if (plansource->gplan) plansource->gplan->is_valid = false; } /* Likewise invalidate cached expressions */ dlist_foreach(iter, &cached_expression_list) { CachedExpression *cexpr = dlist_container(CachedExpression, node, iter.cur); Assert(cexpr->magic == CACHEDEXPR_MAGIC); cexpr->is_valid = false; } } /* * Release all CachedPlans remembered by 'owner' */ void ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner) { ResourceOwnerReleaseAllOfKind(owner, &planref_resowner_desc); } /* ResourceOwner callbacks */ static void ResOwnerReleaseCachedPlan(Datum res) { ReleaseCachedPlan((CachedPlan *) DatumGetPointer(res), NULL); }