1 /*-------------------------------------------------------------------------
4 * Plan cache management.
6 * We can store a cached plan in either fully-planned format, or just
7 * parsed-and-rewritten if the caller wishes to postpone planning until
8 * actual parameter values are available. CachedPlanSource has the same
9 * contents either way, but CachedPlan contains a list of PlannedStmts
10 * and bare utility statements in the first case, or a list of Query nodes
13 * The plan cache manager itself is principally responsible for tracking
14 * whether cached plans should be invalidated because of schema changes in
15 * the tables they depend on. When (and if) the next demand for a cached
16 * plan occurs, the query will be replanned. Note that this could result
17 * in an error, for example if a column referenced by the query is no
18 * longer present. The creator of a cached plan can specify whether it
19 * is allowable for the query to change output tupdesc on replan (this
20 * could happen with "SELECT *" for example) --- if so, it's up to the
21 * caller to notice changes and cope with them.
23 * Currently, we use only relcache invalidation events to invalidate plans.
24 * This means that changes such as modification of a function definition do
25 * not invalidate plans using the function. This is not 100% OK --- for
26 * example, changing a SQL function that's been inlined really ought to
27 * cause invalidation of the plan that it's been inlined into --- but the
28 * cost of tracking additional types of object seems much higher than the
29 * gain, so we're just ignoring them for now.
32 * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
33 * Portions Copyright (c) 1994, Regents of the University of California
36 * $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.20 2008/08/25 22:42:34 tgl Exp $
38 *-------------------------------------------------------------------------
42 #include "utils/plancache.h"
43 #include "access/transam.h"
44 #include "catalog/namespace.h"
45 #include "executor/executor.h"
46 #include "nodes/nodeFuncs.h"
47 #include "storage/lmgr.h"
48 #include "tcop/pquery.h"
49 #include "tcop/tcopprot.h"
50 #include "tcop/utility.h"
51 #include "utils/inval.h"
52 #include "utils/memutils.h"
53 #include "utils/resowner.h"
54 #include "utils/snapmgr.h"
61 } ScanQueryWalkerContext;
70 static List *cached_plans_list = NIL;
72 static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list,
73 MemoryContext plan_context);
74 static void AcquireExecutorLocks(List *stmt_list, bool acquire);
75 static void AcquirePlannerLocks(List *stmt_list, bool acquire);
76 static void LockRelid(Oid relid, LOCKMODE lockmode, void *arg);
77 static void UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg);
78 static void ScanQueryForRelids(Query *parsetree,
81 static bool ScanQueryWalker(Node *node, ScanQueryWalkerContext *context);
82 static bool rowmark_member(List *rowMarks, int rt_index);
83 static bool plan_list_is_transient(List *stmt_list);
84 static void PlanCacheCallback(Datum arg, Oid relid);
85 static void InvalRelid(Oid relid, LOCKMODE lockmode,
86 InvalRelidContext *context);
90 * InitPlanCache: initialize module during InitPostgres.
92 * All we need to do is hook into inval.c's callback list.
97 CacheRegisterRelcacheCallback(PlanCacheCallback, (Datum) 0);
101 * CreateCachedPlan: initially create a plan cache entry.
103 * The caller must already have successfully parsed/planned the query;
104 * about all that we do here is copy it into permanent storage.
106 * raw_parse_tree: output of raw_parser()
107 * query_string: original query text (as of PG 8.4, must not be NULL)
108 * commandTag: compile-time-constant tag for query, or NULL if empty query
109 * param_types: array of parameter type OIDs, or NULL if none
110 * num_params: number of parameters
111 * cursor_options: options bitmask that was/will be passed to planner
112 * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees
113 * fully_planned: are we caching planner or rewriter output?
114 * fixed_result: TRUE to disallow changes in result tupdesc
117 CreateCachedPlan(Node *raw_parse_tree,
118 const char *query_string,
119 const char *commandTag,
127 CachedPlanSource *plansource;
128 OverrideSearchPath *search_path;
129 MemoryContext source_context;
130 MemoryContext oldcxt;
132 Assert(query_string != NULL); /* required as of 8.4 */
135 * Make a dedicated memory context for the CachedPlanSource and its
136 * subsidiary data. We expect it can be pretty small.
138 source_context = AllocSetContextCreate(CacheMemoryContext,
140 ALLOCSET_SMALL_MINSIZE,
141 ALLOCSET_SMALL_INITSIZE,
142 ALLOCSET_SMALL_MAXSIZE);
145 * Fetch current search_path into new context, but do any recalculation
146 * work required in caller's context.
148 search_path = GetOverrideSearchPath(source_context);
151 * Create and fill the CachedPlanSource struct within the new context.
153 oldcxt = MemoryContextSwitchTo(source_context);
154 plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
155 plansource->raw_parse_tree = copyObject(raw_parse_tree);
156 plansource->query_string = pstrdup(query_string);
157 plansource->commandTag = commandTag; /* no copying needed */
160 plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid));
161 memcpy(plansource->param_types, param_types, num_params * sizeof(Oid));
164 plansource->param_types = NULL;
165 plansource->num_params = num_params;
166 plansource->cursor_options = cursor_options;
167 plansource->fully_planned = fully_planned;
168 plansource->fixed_result = fixed_result;
169 plansource->search_path = search_path;
170 plansource->generation = 0; /* StoreCachedPlan will increment */
171 plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
172 plansource->plan = NULL;
173 plansource->context = source_context;
174 plansource->orig_plan = NULL;
177 * Copy the current output plans into the plancache entry.
179 StoreCachedPlan(plansource, stmt_list, NULL);
182 * Now we can add the entry to the list of cached plans. The List nodes
183 * live in CacheMemoryContext.
185 MemoryContextSwitchTo(CacheMemoryContext);
187 cached_plans_list = lappend(cached_plans_list, plansource);
189 MemoryContextSwitchTo(oldcxt);
195 * FastCreateCachedPlan: create a plan cache entry with minimal data copying.
197 * For plans that aren't expected to live very long, the copying overhead of
198 * CreateCachedPlan is annoying. We provide this variant entry point in which
199 * the caller has already placed all the data in a suitable memory context.
200 * The source data and completed plan are in the same context, since this
201 * avoids extra copy steps during plan construction. If the query ever does
202 * need replanning, we'll generate a separate new CachedPlan at that time, but
203 * the CachedPlanSource and the initial CachedPlan share the caller-provided
204 * context and go away together when neither is needed any longer. (Because
205 * the parser and planner generate extra cruft in addition to their real
206 * output, this approach means that the context probably contains a bunch of
207 * useless junk as well as the useful trees. Hence, this method is a
208 * space-for-time tradeoff, which is worth making for plans expected to be
211 * raw_parse_tree, query_string, param_types, and stmt_list must reside in the
212 * given context, which must have adequate lifespan (recommendation: make it a
213 * child of CacheMemoryContext). Otherwise the API is the same as
217 FastCreateCachedPlan(Node *raw_parse_tree,
219 const char *commandTag,
226 MemoryContext context)
228 CachedPlanSource *plansource;
229 OverrideSearchPath *search_path;
230 MemoryContext oldcxt;
232 Assert(query_string != NULL); /* required as of 8.4 */
235 * Fetch current search_path into given context, but do any recalculation
236 * work required in caller's context.
238 search_path = GetOverrideSearchPath(context);
241 * Create and fill the CachedPlanSource struct within the given context.
243 oldcxt = MemoryContextSwitchTo(context);
244 plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
245 plansource->raw_parse_tree = raw_parse_tree;
246 plansource->query_string = query_string;
247 plansource->commandTag = commandTag; /* no copying needed */
248 plansource->param_types = param_types;
249 plansource->num_params = num_params;
250 plansource->cursor_options = cursor_options;
251 plansource->fully_planned = fully_planned;
252 plansource->fixed_result = fixed_result;
253 plansource->search_path = search_path;
254 plansource->generation = 0; /* StoreCachedPlan will increment */
255 plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
256 plansource->plan = NULL;
257 plansource->context = context;
258 plansource->orig_plan = NULL;
261 * Store the current output plans into the plancache entry.
263 StoreCachedPlan(plansource, stmt_list, context);
266 * Since the context is owned by the CachedPlan, advance its refcount.
268 plansource->orig_plan = plansource->plan;
269 plansource->orig_plan->refcount++;
272 * Now we can add the entry to the list of cached plans. The List nodes
273 * live in CacheMemoryContext.
275 MemoryContextSwitchTo(CacheMemoryContext);
277 cached_plans_list = lappend(cached_plans_list, plansource);
279 MemoryContextSwitchTo(oldcxt);
285 * StoreCachedPlan: store a built or rebuilt plan into a plancache entry.
287 * Common subroutine for CreateCachedPlan and RevalidateCachedPlan.
290 StoreCachedPlan(CachedPlanSource *plansource,
292 MemoryContext plan_context)
295 MemoryContext oldcxt;
297 if (plan_context == NULL)
300 * Make a dedicated memory context for the CachedPlan and its
301 * subsidiary data. It's probably not going to be large, but just in
302 * case, use the default maxsize parameter.
304 plan_context = AllocSetContextCreate(CacheMemoryContext,
306 ALLOCSET_SMALL_MINSIZE,
307 ALLOCSET_SMALL_INITSIZE,
308 ALLOCSET_DEFAULT_MAXSIZE);
311 * Copy supplied data into the new context.
313 oldcxt = MemoryContextSwitchTo(plan_context);
315 stmt_list = (List *) copyObject(stmt_list);
319 /* Assume subsidiary data is in the given context */
320 oldcxt = MemoryContextSwitchTo(plan_context);
324 * Create and fill the CachedPlan struct within the new context.
326 plan = (CachedPlan *) palloc(sizeof(CachedPlan));
327 plan->stmt_list = stmt_list;
328 plan->fully_planned = plansource->fully_planned;
330 if (plansource->fully_planned && plan_list_is_transient(stmt_list))
332 Assert(TransactionIdIsNormal(TransactionXmin));
333 plan->saved_xmin = TransactionXmin;
336 plan->saved_xmin = InvalidTransactionId;
337 plan->refcount = 1; /* for the parent's link */
338 plan->generation = ++(plansource->generation);
339 plan->context = plan_context;
341 Assert(plansource->plan == NULL);
342 plansource->plan = plan;
344 MemoryContextSwitchTo(oldcxt);
348 * DropCachedPlan: destroy a cached plan.
350 * Actually this only destroys the CachedPlanSource: the referenced CachedPlan
351 * is released, but not destroyed until its refcount goes to zero. That
352 * handles the situation where DropCachedPlan is called while the plan is
356 DropCachedPlan(CachedPlanSource *plansource)
358 /* Validity check that we were given a CachedPlanSource */
359 Assert(list_member_ptr(cached_plans_list, plansource));
361 /* Remove it from the list */
362 cached_plans_list = list_delete_ptr(cached_plans_list, plansource);
364 /* Decrement child CachePlan's refcount and drop if no longer needed */
365 if (plansource->plan)
366 ReleaseCachedPlan(plansource->plan, false);
369 * If CachedPlanSource has independent storage, just drop it. Otherwise
370 * decrement the refcount on the CachePlan that owns the storage.
372 if (plansource->orig_plan == NULL)
374 /* Remove the CachedPlanSource and all subsidiary data */
375 MemoryContextDelete(plansource->context);
379 Assert(plansource->context == plansource->orig_plan->context);
380 ReleaseCachedPlan(plansource->orig_plan, false);
385 * RevalidateCachedPlan: prepare for re-use of a previously cached plan.
387 * What we do here is re-acquire locks and rebuild the plan if necessary.
388 * On return, the plan is valid and we have sufficient locks to begin
389 * execution (or planning, if not fully_planned).
391 * On return, the refcount of the plan has been incremented; a later
392 * ReleaseCachedPlan() call is expected. The refcount has been reported
393 * to the CurrentResourceOwner if useResOwner is true.
395 * Note: if any replanning activity is required, the caller's memory context
396 * is used for that work.
399 RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
403 /* Validity check that we were given a CachedPlanSource */
404 Assert(list_member_ptr(cached_plans_list, plansource));
407 * If the plan currently appears valid, acquire locks on the referenced
408 * objects; then check again. We need to do it this way to cover the race
409 * condition that an invalidation message arrives before we get the lock.
411 plan = plansource->plan;
412 if (plan && !plan->dead)
415 * Plan must have positive refcount because it is referenced by
416 * plansource; so no need to fear it disappears under us here.
418 Assert(plan->refcount > 0);
420 if (plan->fully_planned)
421 AcquireExecutorLocks(plan->stmt_list, true);
423 AcquirePlannerLocks(plan->stmt_list, true);
426 * If plan was transient, check to see if TransactionXmin has
427 * advanced, and if so invalidate it.
430 TransactionIdIsValid(plan->saved_xmin) &&
431 !TransactionIdEquals(plan->saved_xmin, TransactionXmin))
435 * By now, if any invalidation has happened, PlanCacheCallback will
436 * have marked the plan dead.
440 /* Ooops, the race case happened. Release useless locks. */
441 if (plan->fully_planned)
442 AcquireExecutorLocks(plan->stmt_list, false);
444 AcquirePlannerLocks(plan->stmt_list, false);
449 * If plan has been invalidated, unlink it from the parent and release it.
451 if (plan && plan->dead)
453 plansource->plan = NULL;
454 ReleaseCachedPlan(plan, false);
459 * Build a new plan if needed.
464 TupleDesc resultDesc;
467 * Restore the search_path that was in use when the plan was made.
468 * (XXX is there anything else we really need to restore?)
470 PushOverrideSearchPath(plansource->search_path);
473 * Run parse analysis and rule rewriting. The parser tends to
474 * scribble on its input, so we must copy the raw parse tree to
475 * prevent corruption of the cache. Note that we do not use
476 * parse_analyze_varparams(), assuming that the caller never wants the
477 * parameter types to change from the original values.
479 slist = pg_analyze_and_rewrite(copyObject(plansource->raw_parse_tree),
480 plansource->query_string,
481 plansource->param_types,
482 plansource->num_params);
484 if (plansource->fully_planned)
487 * Generate plans for queries.
489 * If a snapshot is already set (the normal case), we can just use
490 * that for planning. But if it isn't, we have to tell
491 * pg_plan_queries to make a snap if it needs one.
493 slist = pg_plan_queries(slist, plansource->cursor_options,
494 NULL, !ActiveSnapshotSet());
498 * Check or update the result tupdesc. XXX should we use a weaker
499 * condition than equalTupleDescs() here?
501 resultDesc = PlanCacheComputeResultDesc(slist);
502 if (resultDesc == NULL && plansource->resultDesc == NULL)
504 /* OK, doesn't return tuples */
506 else if (resultDesc == NULL || plansource->resultDesc == NULL ||
507 !equalTupleDescs(resultDesc, plansource->resultDesc))
509 MemoryContext oldcxt;
511 /* can we give a better error message? */
512 if (plansource->fixed_result)
514 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
515 errmsg("cached plan must not change result type")));
516 oldcxt = MemoryContextSwitchTo(plansource->context);
518 resultDesc = CreateTupleDescCopy(resultDesc);
519 if (plansource->resultDesc)
520 FreeTupleDesc(plansource->resultDesc);
521 plansource->resultDesc = resultDesc;
522 MemoryContextSwitchTo(oldcxt);
525 /* Now we can restore current search path */
526 PopOverrideSearchPath();
529 * Store the plans into the plancache entry, advancing the generation
532 StoreCachedPlan(plansource, slist, NULL);
534 plan = plansource->plan;
538 * Last step: flag the plan as in use by caller.
541 ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
544 ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
550 * ReleaseCachedPlan: release active use of a cached plan.
552 * This decrements the reference count, and frees the plan if the count
553 * has thereby gone to zero. If useResOwner is true, it is assumed that
554 * the reference count is managed by the CurrentResourceOwner.
556 * Note: useResOwner = false is used for releasing references that are in
557 * persistent data structures, such as the parent CachedPlanSource or a
558 * Portal. Transient references should be protected by a resource owner.
561 ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
564 ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan);
565 Assert(plan->refcount > 0);
567 if (plan->refcount == 0)
568 MemoryContextDelete(plan->context);
572 * AcquireExecutorLocks: acquire locks needed for execution of a fully-planned
573 * cached plan; or release them if acquire is false.
576 AcquireExecutorLocks(List *stmt_list, bool acquire)
580 foreach(lc1, stmt_list)
582 PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc1);
586 Assert(!IsA(plannedstmt, Query));
587 if (!IsA(plannedstmt, PlannedStmt))
588 continue; /* Ignore utility statements */
591 foreach(lc2, plannedstmt->rtable)
593 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
598 if (rte->rtekind != RTE_RELATION)
602 * Acquire the appropriate type of lock on each relation OID. Note
603 * that we don't actually try to open the rel, and hence will not
604 * fail if it's been dropped entirely --- we'll just transiently
605 * acquire a non-conflicting lock.
607 if (list_member_int(plannedstmt->resultRelations, rt_index))
608 lockmode = RowExclusiveLock;
609 else if (rowmark_member(plannedstmt->rowMarks, rt_index))
610 lockmode = RowShareLock;
612 lockmode = AccessShareLock;
615 LockRelationOid(rte->relid, lockmode);
617 UnlockRelationOid(rte->relid, lockmode);
623 * AcquirePlannerLocks: acquire locks needed for planning and execution of a
624 * not-fully-planned cached plan; or release them if acquire is false.
626 * Note that we don't actually try to open the relations, and hence will not
627 * fail if one has been dropped entirely --- we'll just transiently acquire
628 * a non-conflicting lock.
631 AcquirePlannerLocks(List *stmt_list, bool acquire)
635 foreach(lc, stmt_list)
637 Query *query = (Query *) lfirst(lc);
639 Assert(IsA(query, Query));
641 ScanQueryForRelids(query, LockRelid, NULL);
643 ScanQueryForRelids(query, UnlockRelid, NULL);
648 * ScanQueryForRelids callback functions for AcquirePlannerLocks
651 LockRelid(Oid relid, LOCKMODE lockmode, void *arg)
653 LockRelationOid(relid, lockmode);
657 UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg)
659 UnlockRelationOid(relid, lockmode);
663 * ScanQueryForRelids: recursively scan one Query and apply the callback
664 * function to each relation OID found therein. The callback function
665 * takes the arguments relation OID, lockmode, pointer arg.
668 ScanQueryForRelids(Query *parsetree,
676 * First, process RTEs of the current query level.
679 foreach(lc, parsetree->rtable)
681 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
685 switch (rte->rtekind)
690 * Determine the lock type required for this RTE.
692 if (rt_index == parsetree->resultRelation)
693 lockmode = RowExclusiveLock;
694 else if (rowmark_member(parsetree->rowMarks, rt_index))
695 lockmode = RowShareLock;
697 lockmode = AccessShareLock;
699 (*callback) (rte->relid, lockmode, arg);
705 * The subquery RTE itself is all right, but we have to
706 * recurse to process the represented subquery.
708 ScanQueryForRelids(rte->subquery, callback, arg);
712 /* ignore other types of RTEs */
718 * Recurse into sublink subqueries, too. But we already did the ones in
721 if (parsetree->hasSubLinks)
723 ScanQueryWalkerContext context;
725 context.callback = callback;
727 query_tree_walker(parsetree, ScanQueryWalker,
729 QTW_IGNORE_RT_SUBQUERIES);
734 * Walker to find sublink subqueries for ScanQueryForRelids
737 ScanQueryWalker(Node *node, ScanQueryWalkerContext *context)
741 if (IsA(node, SubLink))
743 SubLink *sub = (SubLink *) node;
745 /* Do what we came for */
746 ScanQueryForRelids((Query *) sub->subselect,
747 context->callback, context->arg);
748 /* Fall through to process lefthand args of SubLink */
752 * Do NOT recurse into Query nodes, because ScanQueryForRelids already
753 * processed subselects of subselects for us.
755 return expression_tree_walker(node, ScanQueryWalker,
760 * rowmark_member: check whether an RT index appears in a RowMarkClause list.
763 rowmark_member(List *rowMarks, int rt_index)
769 RowMarkClause *rc = (RowMarkClause *) lfirst(l);
771 if (rc->rti == rt_index)
778 * plan_list_is_transient: check if any of the plans in the list are transient.
781 plan_list_is_transient(List *stmt_list)
785 foreach(lc, stmt_list)
787 PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
789 if (!IsA(plannedstmt, PlannedStmt))
790 continue; /* Ignore utility statements */
792 if (plannedstmt->transientPlan)
800 * PlanCacheComputeResultDesc: given a list of either fully-planned statements
801 * or Queries, determine the result tupledesc it will produce. Returns NULL
802 * if the execution will not return tuples.
804 * Note: the result is created or copied into current memory context.
807 PlanCacheComputeResultDesc(List *stmt_list)
813 switch (ChoosePortalStrategy(stmt_list))
815 case PORTAL_ONE_SELECT:
816 node = (Node *) linitial(stmt_list);
817 if (IsA(node, Query))
819 query = (Query *) node;
820 return ExecCleanTypeFromTL(query->targetList, false);
822 if (IsA(node, PlannedStmt))
824 pstmt = (PlannedStmt *) node;
825 return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
827 /* other cases shouldn't happen, but return NULL */
830 case PORTAL_ONE_RETURNING:
831 node = PortalListGetPrimaryStmt(stmt_list);
832 if (IsA(node, Query))
834 query = (Query *) node;
835 Assert(query->returningList);
836 return ExecCleanTypeFromTL(query->returningList, false);
838 if (IsA(node, PlannedStmt))
840 pstmt = (PlannedStmt *) node;
841 Assert(pstmt->returningLists);
842 return ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists), false);
844 /* other cases shouldn't happen, but return NULL */
847 case PORTAL_UTIL_SELECT:
848 node = (Node *) linitial(stmt_list);
849 if (IsA(node, Query))
851 query = (Query *) node;
852 Assert(query->utilityStmt);
853 return UtilityTupleDescriptor(query->utilityStmt);
855 /* else it's a bare utility statement */
856 return UtilityTupleDescriptor(node);
858 case PORTAL_MULTI_QUERY:
859 /* will not return tuples */
867 * Relcache inval callback function
869 * Invalidate all plans mentioning the given rel, or all plans mentioning
870 * any rel at all if relid == InvalidOid.
873 PlanCacheCallback(Datum arg, Oid relid)
878 foreach(lc1, cached_plans_list)
880 CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
881 CachedPlan *plan = plansource->plan;
883 /* No work if it's already invalidated */
884 if (!plan || plan->dead)
886 if (plan->fully_planned)
888 foreach(lc2, plan->stmt_list)
890 PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
892 Assert(!IsA(plannedstmt, Query));
893 if (!IsA(plannedstmt, PlannedStmt))
894 continue; /* Ignore utility statements */
895 if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL :
896 list_member_oid(plannedstmt->relationOids, relid))
898 /* Invalidate the plan! */
900 break; /* out of stmt_list scan */
907 * For not-fully-planned entries we use ScanQueryForRelids, since
908 * a recursive traversal is needed. The callback API is a bit
909 * tedious but avoids duplication of coding.
911 InvalRelidContext context;
913 context.inval_relid = relid;
916 foreach(lc2, plan->stmt_list)
918 Query *query = (Query *) lfirst(lc2);
920 Assert(IsA(query, Query));
921 ScanQueryForRelids(query, InvalRelid, (void *) &context);
928 * ResetPlanCache: drop all cached plans.
933 PlanCacheCallback((Datum) 0, InvalidOid);
937 * ScanQueryForRelids callback function for PlanCacheCallback
940 InvalRelid(Oid relid, LOCKMODE lockmode, InvalRelidContext *context)
942 if (relid == context->inval_relid || context->inval_relid == InvalidOid)
943 context->plan->dead = true;